Home > java > Utilizzo dei Socket su protocolli TCP/IP

Utilizzo dei Socket su protocolli TCP/IP

10 luglio 2010

Translate in English with Google Translate
Introduzione
Le Socket costituiscono un canale di comunicazione che permette di inviare e ricevere dei dati tramite la rete appoggiandosi su un protocollo di comunicazione.
Letteralmente Socket significa “presa” infatti, potrebbe essere paragonata ad una presa elettrica, mentre il flusso di dati potrebbe essere paragonato alla corrente che passa attraverso il cavo.
La loro origine risale al 1983, quando furono introdotti nel BSD 4.2 (BSD-Berkeley Software Distribution) nella Università di Berkeley in California.
I protocolli utilizzabili per l’implementazione delle Socket sono:

  • il protocollo TCP (Transfer Control Protocol)
  • il protocollo UDP(User Datagram Protocol)

Entrambi questi protocolli si appoggiano sul protocollo IP (Internet Protocol)

Protocollo TCP
Il protocollo TCP fornisce un servizio di consegna di pacchetti affidabile e orientato alla connessione (Connection-Oriented Acknowledged Transport Protocol).
Il protocollo TCP è affidabile in quanto:

  • garantisce la consegna dei pacchetti
  • esegue controlli sull’integrità dei dati trasmessi utilizzando i calcoli di checksum
  • garantisce l’appropriata sequenza e l’ordinata consegna dei dati segmentati
  • invia messaggi positivi nel caso in cui i dati siano stati ricevuti ed avvisi negativi per i dati non ricevuti;

questo permette di evitare di duplicare l’invio di un segmento oppure di rinviarlo in caso di errori nel trasferimento.

Il protocollo TCP è orientato alla connessione in quanto viene realizzata una connessione tra i due host ed i dati vengono suddivisi in tanti segmenti numerati che vengono passati attraverso uno stream di byte.

I principali vantaggi di questo protocollo sono dati dalla possibilità di trasferire con sicurezza una grande quantità di dati; inoltre, in presenza di una rete congestionata, questo protocollo garantisce il corretto instradamento dei pacchetti.
Gli svantaggi sono legati alla lentezza nel trasferimento dei dati, a causa dei controlli da realizzare per assicurarsi un effettivo trasferimento dei pacchetti e per la dimensione del segmento. Inoltre il supporto è esclusivo per le comunicazione di tipo Point To Point(PTP) indirizzate verso un unico destinatario e non di tipo Point To Multipoint (PTM) verso più destinatari.

Esempi di protocolli TCP utilizzati: FTP –Telnet –HTTP.

Testata TCP
La descrizione completa del protocollo TCP è contenuta nel documento RFC793. Di seguito viene riportata la testata del protocollo TCP e la sua descrizione.

  • I campi Source Port e Destination Port uniti ai campi Indirizzo Sorgente ed Indirizzo destinazione identificano univocamente una connessione.
  • Il Sequence Number identifica il byte dello stream originario rappresentato dal primo byte del segmento corrente.
  • L’ Acknowledgment Number è il numero di sequenza che il ricevente si attende di ricevere nel prossimo segmento.
  • Data Offset è la lunghezza della testata, comprensiva di opzioni. La dimensione massima della testata è di 60 byte, senza opzioni la lunghezza è di 30 byte.
  • I Flag sono sei:
    1. URG – il Puntatore Dati Urgenti è valido
    2. ACK – il Numero Conferma è valido
    3. PSH – il ricevente deve passare queste informazioni all’applicativo nel più breve tempo possibile
    4. RST – reset di connessione
    5. SYN – sincronizzare i numeri di sequenza per iniziare una connessione
    6. FIN – il trasmittente ha finito l’invio dei dati.
  • Il campo Window esprime la lunghezza della finestra di trasmissione in byte. TCP è un protocollo a finestra di trasmissione scorrevole senza ritrasmissione selettiva.
  • Il Checksum è il campo di controllo consistenza, calcolato col normale algoritmo di IP, e copre l’intero segmento.
  • Urgent Pointer è l’offset da aggiungere al Sequence Number per ottenere il numero di sequenza dell’ultimo byte di dati urgenti. TCP offre la possibilità di indicare dati di emergenza.
  • Ci sono varie Options ammissibili, di cui la più importante è l’indicazione della dimensione massima di segmento ( Maximum Segment Size – MSS ). Quest’opzione viene normalmente scambiata nel primo segmento di una connessione.
  • Il campo data è opzionale e in alcuni casi non viene usato. In tal caso il segmento TCP serve allo scambio di informazioni di controllo.

Il segmento TCP è incapsulato in un pacchetto IP. La lunghezza della testata TCP è di 20 byte.

Porte TCP
Affinché su una determinata macchina possano essere attivati più servizi, deve essere indicata il numero di porta corrispondente ad un determinato servizio.
Le porte sono rappresentate da un numero di 2 byte (16 bit), pertanto è possibile utilizzare un numero compreso tra 0 e 65536.
I numeri delle porte sono divisi in tre gruppi:

  • Well-Known-Ports (0 –1023): queste porte sono assegnate univocamente dall’ Internet Assigned Numbers Authority (IANA).
  • Registered Ports (1024 –49151): l’ di queste porte viene registrato a beneficio degli utenti della rete, ma non esistono vincoli restrittivi.
  • Dynamic and/or Private Ports (49152 –65535): non viene applicato nessun controllo all’ di queste porte.

Per esempio:

Descrizione Servizio Porta TCP
FTP (File Transfer Protocol) 20 – 21
Telnet 23
SMTP (Simple Mail Transfer Protocol) 25
HTTP (Hypertext Transfer Protocol) 80
POP3 (Post Office Protocol) 110

Socket sul protocollo TCP
In Java le Socket sono implementate con classi distinte a seconda che si intenda utilizzarle sul protocollo TCP o UDP.
Per il protocollo TCP vengono utilizzate le classi:

  • ServerSocket: socket TCP lato server
  • Socket: socket TCP lato client.

Di seguito vengono realizzati il Server ed il Client attraverso l’utilizzo delle classi ServerSocket e Socket.

Creazione del Server TCP

Al contrario di altri linguaggi, come per esempio C, scrivere un’applicazione in Java che comunichi via socket è alquanto semplice. Per prima cosa è necessario aprire questo socket creando un’istanza della classe Socket che rappresenta l’asse portante del programma ed infatti tramite questa classe è possibile realizzare la comunicazione, poi assegnare uno Stream per l’output e uno per l’input, in maniera tale che sia possibile usare i metodi preintln() e readLine(), come se si avesse a che fare con l’input/output da tastiera.
Finito il tutto si deve, naturalmente, chiudere gli Stream ed il Socket.
Per realizzare una comunicazione Client/Server avvalendosi dei socket, occorrerà creare un Server in ascolto su una determinata porta ed un Client che effettui una chiamata.
Per creare un Server in ascolto occorre seguire alcuni passaggi:

    1. importare i package java.io e java.net per poter utilizzare le classi relative allo streaming ed alle Socket;
    2. intercettare l’ eccezione IOException che deve essere gestita o propagata poiché vengono richiamati dei metodi delle classi appartenenti ai package java.io e java.net che sollevano questa eccezione;
    3. creare un oggetto ServerSocket utilizzando il costruttore ServerSocket(int port) che accetta come parametro la porta del Server in ascolto per il servizio da attivare.In questo esempio è stata utilizzata la porta 7777.
ServerSocket serverSocket = new ServerSocket(7777);
    1. richiamare il metodo accept() sull’oggetto ServerSocket per poter accettare le chiamate da parte del Client su un oggetto Socket .
Socket socket = serverSocket.accept();
    1. Creare due oggetti DataInputStream e DataOutputStream per processare le richieste del Client, avvalendosi di uno stream di byte in input ed output per realizzazione la comunicazione con il Client
DataInputStream is = new DataInputStream(socket.getInputStream());
DataOutputStream os = new DataOutputStream(socket.getOutputStream());
    1. richiamare il metodo close() sugli oggetti utilizzati per lo streaming al termine della comunicazione, per chiudere gli oggetti utilizzati
os.close();
is.close();
  1. Richiamare il metodo close() sull’oggetto Socket per chiudere la connessione con il Client quando esso farà una richiesta di chiusura socket.close();.

Realizzazione di un semplice Server TCP

//Importo i package necessari
import java.net.*;
import java.io.*;
   public class TCPServer {
     public void start() throws IOException {
       ServerSocket serverSocket = new ServerSocket(7777);
       //Informazioni sul Server in ascolto
       InetAddress indirizzo = serverSocket.getInetAddress();
       String server = indirizzo.getHostAddress();
       int port = serverSocket.getLocalPort();
       System.out.println("In ascolto Server: "+ server + " porta: " + port);
       //Ciclo infinito per ascolto dei Client
       while (true) {
         System.out.println("In attesa di chiamate dai Client... ");
         Socket socket = serverSocket.accept();
         //Informazioni sul Client che ha effettuato la chiamata
         InetAddress address = socket.getInetAddress();
         String client = address.getHostName();
         int porta = socket.getPort();
         System.out.println("In chiamata Client: "+ client + " porta: " + porta);
         //Stream di byte utilizzato per la comunicazione via socket
         DataInputStream is = new DataInputStream(socket.getInputStream());
         DataOutputStream os = new DataOutputStream(socket.getOutputStream());
         while (true) {
           String userInput = is.readLine();
           if (userInput == null || userInput.equals("QUIT"))
             break;
           os.writeBytes(userInput + '\n');
           System.out.println("Il Client ha scritto: " + userInput);
         }
         //chiusura della comunicazione con il Client
         os.close();
         is.close();
         System.out.println("Chiusura chiamata Client: " + client + "su porta:" + porta);
         socket.close();
       }
     }
     public static void main (String[] args) throws Exception {
       TCPServer tcpServer = new TCPServer();
       tcpServer.start();
     }
   }

Creazione del Client TCP

Per creare un Client che effettui una chiamata ad un Server occorre seguire questi passaggi:

  1. importare i package java.io, java.net per poter utilizzare le classi relative allo Stream ed alle Socket.
  2. Intercettare l’eccezione IOException che deve essere gestita o propagata poiché vengono richiamati dei metodi delle classi appartenenti ai package java.io e java.net che sollevano questa eccezione.
  3. Creare un oggetto Socket e specificare l’indirizzo IP ed il numero di porta in ascolto sul Server. Come abbiamo visto a proposito del protocollo TCP/IP, affinché avvenga una comunicazione tra due host occorre che si disponga degli indirizzi IP e delle porte del Client e del Server.
    Per il server : bisogna specificare il suo indirizzo IP o il nome ed inoltre bisogna specificare la porta in ascolto per il servizio che si intende utilizzare. Nell’ esempio proposto precedentemente sul server è stata attivata la porta 7777, pertanto verrà fatta una richiesta di connessione su quella porta.

    Socket socket = new Socket("localhost", 7777);.

    Se si vuole ottenere l’indirizzo IP e il numero di porta del server attivato su un determinato oggetto socket possiamo utilizzare i metodi:

    getInetAddress() della classe InetAddress;
    getPort() della classe Socket.

    Per il client l’indirizzo IP viene ricavato mentre la porta viene assegnata dinamicamente in base alla disponibilità.
    Se si vuole conoscere l’indirizzo IP e la porta del Client, è possibile utilizzare i metodi:

    getLocalAddress() della classe InetAddress;
    getLocalPort() della classe Socket.
  4. Comunicare con il Server per inviare e ricevere messaggi tramite gli stream di byte in input ed output utilizzando le classi DataInputStream e DataOutputStream. Questo permette al Client di poter ottenere in input ed output uno stream di byte tramite il socket utilizzando i metodi getOutputStream() e getInputStream():
    DataOutputStream os = new DataOutputStream(socket.getOutputStream());
    DataInputStream is = new DataInputStream(socket.getInputStream());
    
  5. Chiudere gli stream di byte in input ed output sugli oggetti DataInputStream e DataOutputStream utilizzando il metodo close():
    os.close();
    is.close();
    
  6. Chiudere la socket richiamando il metodo close() sull’oggetto Socket . Questo permette di comunicare al Server la chiusura della socket relativo alla comunicazione realizzata:socket.close();

Realizzazione di un semplice Client TCP

//Importo i package necessari
import java.net.*;
import java.io.*;
   public class TCPClient {
     public void start()throws IOException {
       //Connessione della Socket con il Server
       Socket socket = new Socket("localhost", 7777);
       //Stream di byte da passare al Socket
       DataOutputStream os = new DataOutputStream(socket.getOutputStream());
       DataInputStream is = new DataInputStream(socket.getInputStream());
       BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
       System.out.print("Per disconnettersi dal Server scrivere: QUIT\n");
       //Ciclo infinito per inserimento testo del Client
       while (true) {
         System.out.print("Inserisci: ");
         String userInput = stdIn.readLine();
         if (userInput.equals("QUIT"))
           break;
         os.writeBytes(userInput + '\n');
         System.out.println("Hai digitato: " + is.readLine());
       }
       //Chiusura dello Stream e del Socket
       os.close();
       is.close();
       socket.close();
     }
     public static void main (String[] args) throws Exception {
       TCPClient tcpClient = new TCPClient();
       tcpClient.start();
     }
   }

Server in multithreading

L’ esempio client/server realizzato effettua un semplice servizio di ECHO dei messaggi inviati dal client, per poter mettere in evidenza solamente la comunicazione tramite le Socket, ma un server si occupa di gestire operazioni molto più complesse.
Il server realizzato non utilizza il multithreading; pertanto è possibile servire le chiamate di un solo client alla volta. Solo quando il client si disconnette sarà possibile servire gli altri. Normalmente un server fa uso del multithreading per servire più client estendendo la classe Thread o implementando l’interfaccia Runnable.

//Importo i package
import java.net.*;
import java.io.*;
   //Creazione di una classe per il Multrithreading
   class ServerThread extends Thread {
     private Socket socket;
     public ServerThread (Socket socket) {
       this.socket = socket;
     }
     //esecuzione del Thread sul Socket
     public void run() {
       try {
         DataInputStream is = new DataInputStream(socket.getInputStream());
         DataOutputStream os = new DataOutputStream(socket.getOutputStream());
         while(true) {
           String userInput = is.readLine();
           if (userInput == null || userInput.equals("QUIT"))
             break;
           os.writeBytes(userInput + '\n');
           System.out.println("Il Client ha scritto: " + userInput);
         }
         os.close();
         is.close();
         System.out.println("Ho ricevuto una chiamata di chiusura da:\n" + socket +
"\n");
         socket.close();
       }
       catch (IOException e) {
         System.out.println("IOException: " + e);
       }
     }
   }
   //Classe Server per attivare la Socket
   public class TCPParallelServer {
     public void start() throws Exception {
       ServerSocket serverSocket = new ServerSocket(7777);
       //Ciclo infinito di ascolto dei Client
       while(true) {
         System.out.println("In attesa di chiamate dai Client... ");
         Socket socket = serverSocket.accept();
         System.out.println("Ho ricevuto una chiamata di apertura da:\n" + socket);
         ServerThread serverThread = new ServerThread(socket);
         serverThread.start();
       }
     }
     public static void main (String[] args) throws Exception {
       TCPParallelServer tcpServer = new TCPParallelServer();
       tcpServer.start();
     }
   }

Conclusioni
Attraverso l’utilizzo del protocollo TCP/IP abbiamo realizzato una connessione Client/Server che ci consente di sfruttare le garanzie del protocollo TCP/IP. Le classi ServerSocket e Socket ci consentono di poter creare in modo molto semplice una connessione e di comunicare tramite uno stream di byte.

Categorie:java
  1. fabio
    15 aprile 2013 alle 5:28 PM

    ottimo ottimo e ancora ottimo articolo …
    non solo funziona tutto alla grande ma veramente spiega bene il tutto …
    avanzi un mega pizza + birra
    ciao
    fabio

  2. daniele
    3 dicembre 2013 alle 7:28 PM

    Mi avevi spiegato di persona questi concetti, ed ora riguardandoli riesco ad apprezzare a pieno spiegazioni ed esempi!
    Ottima guida!!!
    a presto
    daniele

    • 4 dicembre 2013 alle 2:47 AM

      Capita spesso anche a me di rileggere le stesse cose a distanza di tempo ed apprezzarle appieno, in quei momenti riesco a “pesare” ogni parola ossia a “capire” cosa voleva dire. E’ un buon segno, vuol dire che abbiamo capito! Ciao Daniele.

  1. No trackbacks yet.
I commenti sono chiusi.
%d blogger cliccano Mi Piace per questo: