Quando si parla di networking non si può non citare il modello OSI.

In termini di questo modello, oggi siamo più interessati al livello di trasporto (4).

Questo è il livello al quale lavoriamo con i dati che si spostano "dal punto A al punto B". Il compito principale del livello di trasporto è garantire che un messaggio venga consegnato alla destinazione, mantenendo la sequenza corretta. I due protocolli del livello di trasporto più comuni sono: TCP e UDP. Funzionano concettualmente in modi diversi, ma ognuno ha i suoi vantaggi che consentono loro di risolvere problemi specifici.

Per prima cosa, diamo un'occhiata a come funziona il TCP.

TCP (Transmission Control Protocol) è un protocollo di rete che garantisce che venga stabilita una connessione tra gli host prima che i dati vengano scambiati.

Questo è un protocollo molto affidabile, perché ogni volta che invia un altro pacchetto di dati, deve verificare che il pacchetto precedente sia stato ricevuto.

I pacchetti trasmessi vengono ordinati e, se si verificano problemi con un determinato pacchetto (ad esempio, la parte ricevente non conferma l'arrivo del pacchetto), il pacchetto viene nuovamente inviato. Di conseguenza, la velocità di trasferimento è relativamente bassa, poiché è necessario più tempo per il monitoraggio rigoroso e per garantire l'ordine corretto.

È qui che entra in gioco il suo "fratello", il protocollo UDP. A differenza di TCP, UDP non si preoccupa realmente dell'ordine e dello stato di ciascun pacchetto. Invia semplicemente i dati senza conferma di consegna. Inoltre, non stabilisce una connessione e non dipende in alcun modo dallo stato della connessione.

Il suo scopo è semplicemente quello di inviare dati a un indirizzo. E questo dà origine al principale svantaggio del protocollo, la scarsa affidabilità, poiché può semplicemente perdere pezzi di dati. Inoltre, il destinatario deve essere preparato al fatto che i dati potrebbero arrivare fuori servizio. Detto questo, il protocollo ha anche un vantaggio, una velocità di trasferimento più elevata, dovuta al fatto che il protocollo si limita all'invio di dati.

Ci sono anche differenze nel modo in cui i dati stessi vengono trasmessi. In TCP, i dati vengono trasmessi in streaming, il che significa che i dati non hanno confini. In UDP, i dati vengono trasmessi come datagrammi e hanno limiti e il destinatario verifica l'integrità dei dati, ma solo se il messaggio viene ricevuto correttamente.

Riassumiamo:

TCP è un protocollo affidabile e preciso che previene la perdita di dati. Un messaggio verrà sempre recapitato con la massima precisione o non recapitato affatto. Il destinatario non ha bisogno di una logica per ordinare i dati, poiché i dati in arrivo saranno già ordinati. UDP non è così affidabile, ma è un protocollo di trasferimento dati più veloce. Le parti di invio e ricezione necessitano di una logica aggiuntiva per lavorare con questo protocollo. Ma diamo un'occhiata a come funziona usando l'esempio di un gioco per computer o di un gioco per cellulare giocato in rete. Potremmo non preoccuparci più di ciò che sarebbe dovuto arrivare 5 secondi fa e potremmo saltare un paio di pacchetti se non arrivano in tempo: il gioco potrebbe essere in ritardo, ma puoi ancora giocare!

In Java, per lavorare con i datagrammi trasmessi su UDP, utilizziamo oggetti delle classi DatagramSocket e DatagramPacket .

Per scambiare i dati, il mittente e il destinatario creano datagram socket, cioè istanze della classe DatagramSocket . La classe ha diversi costruttori. La differenza tra loro è dove si collegherà il socket creato:

DatagramSocket () Si connette a qualsiasi porta disponibile sulla macchina locale
DatagramSocket (porta int) Si connette alla porta specificata sul computer locale
DatagramSocket(porta int, InetAddress addr) Si connette alla porta specificata a un indirizzo sulla macchina locale (addr)

La classe contiene molti metodi per accedere e gestire i parametri del socket (li esamineremo un po' più avanti), oltre a metodi per ricevere e inviare datagrammi:

invio (Pacchetto pacchetto dati) Invia datagrammi impacchettati in pacchetti
ricevere (pacchetto DatagramPacket) Riceve datagrammi impacchettati in pacchetti

DatagramPacket è una classe che rappresenta un pacchetto di datagrammi. I pacchetti di datagrammi vengono utilizzati per implementare un servizio di consegna dei pacchetti senza connessione. Ogni messaggio viene instradato da una macchina all'altra in base esclusivamente alle informazioni contenute in quel pacchetto. Più pacchetti inviati da una macchina a un'altra possono essere instradati in modo diverso e possono arrivare in qualsiasi ordine. La consegna dei pacchetti non è garantita.

Costruttori:

DatagramPacket(byte[] buf, int lunghezza) Crea un oggetto DatagramPacket per accettare pacchetti di lunghezza length .
DatagramPacket(byte[] buf, lunghezza int, indirizzo InetAddress, porta int) Crea un pacchetto di datagrammi per inviare pacchetti di lunghezza al numero di porta specificato sull'host specificato.
DatagramPacket(byte[] buf, int offset, int lunghezza) Crea un oggetto DatagramPacket per accettare pacchetti di lunghezza length , specificando un offset nel buffer.
DatagramPacket(byte[] buf, offset int, lunghezza int, indirizzo InetAddress, porta int) Crea un pacchetto datagramma per inviare pacchetti di lunghezza lunghezza con offset offset al numero di porta specificato sull'host specificato.
DatagramPacket(byte[] buf, int offset, int lunghezza, indirizzo SocketAddress) Crea un pacchetto datagramma per inviare pacchetti di lunghezza lunghezza con offset offset al numero di porta specificato sull'host specificato.
DatagramPacket(byte[] buf, lunghezza int, indirizzo SocketAddress) Crea un pacchetto di datagrammi per inviare pacchetti di lunghezza al numero di porta specificato sull'host specificato.

Ricordiamo che l'approccio UDP non stabilisce una connessione. I pacchetti vengono spediti nella speranza che il destinatario li stia aspettando. Ma puoi stabilire una connessione usando il metodo connect(InetAddress addr, int port) della classe DatagramSocket .

Viene stabilita una connessione unidirezionale con l'host sulla base di un indirizzo e di una porta: per inviare o ricevere datagrammi. La connessione può essere terminata utilizzando il metodo disconnect() .

Proviamo a scrivere codice server basato su DatagramSocket per ricevere i dati:


import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

class Recipient {

   public static void main(String[] args) {
       try {
           DatagramSocket ds = new DatagramSocket(1050);

           while (true) {
               DatagramPacket pack = new DatagramPacket(new byte[5], 5);
               ds.receive(pack);
               System.out.println(new String(pack.getData()));
           }
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
}

Creiamo un oggetto DatagramSocket in ascolto sulla porta 1050. Quando riceve un messaggio, lo stampa sulla console. Trasmetteremo la parola "Ciao", quindi limitiamo la dimensione del buffer a cinque byte.

Ora creeremo la classe mittente:


import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

class Sender {
   private String host;
   private int port;

   Sender(String host, int port) {
       this.host = host;
       this.port = port;
   }

   private void sendMessage(String mes) {
       try {
           byte[] data = mes.getBytes();
           InetAddress address = InetAddress.getByName(host);
           DatagramPacket pack = new DatagramPacket(data, data.length, address, port);
           DatagramSocket ds = new DatagramSocket();
           ds.send(pack);
           ds.close();
       } catch (IOException e) {
           System.err.println(e);
       }
   }

   public static void main(String[] args) {
   Sender sender = new Sender("localhost", 1050);
   String message = "Hello";

   Timer timer = new Timer();
   timer.scheduleAtFixedRate(new TimerTask() {
       @Override
       public void run() {
           sender.sendMessage(message);
       }
   }, 1000, 1000);
}

}

Nel metodo sendMessage , creiamo un DatagramPacket e un DatagramSocket e inviamo il nostro messaggio. Si noti che il metodo close() viene utilizzato per chiudere il DatagramSocket dopo l'invio del messaggio.

Ogni secondo la console del destinatario visualizza il messaggio "Ciao" in arrivo inviato dal mittente. Ciò significa che la nostra comunicazione funziona correttamente.