Abbiamo finora visto come la comunicazione in java tra un client e server avviene attraverso le socket: le socket creano un canale tra il client e server attraverso cui le due applicazioni comunicano. Il protocollo di comunicazione a livello trasporto è il TCP (Trasmission Control Protocol).
Le classi come visto per stabilire una connessione di tipo TCP/IP tra client e server sono java.net.Socket e java.net.ServerSocket.
Java mette a disposizione anche la possibilità di creare una connessione su protocollo UDP (User Datagram Protocol) tramite le classi java.net.Datagram, per il messaggio datagram, e java.net.DatagramSocket, la socket per l'invio e la ricezione del messaggio datagram.
Transmission Control Protocol is a connection-oriented protocol, which means that it requires handshaking to set up end-to-end communications. Once a connection is set up, user data may be sent bi-directionally over the connection.
Reliable – Strictly only at transport layer, TCP manages message acknowledgment, retransmission and timeout. Multiple attempts to deliver the message are made. If it gets lost along the way, the server will re-request the lost part. In TCP, there's either no missing data, or, in case of multiple timeouts, the connection is dropped. (This reliability however does not cover application layer, at which a separate acknowledgement flow control is still necessary)
Ordered – If two messages are sent over a connection in sequence, the first message will reach the receiving application first. When data segments arrive in the wrong order, TCP buffers delay the out-of-order data until all data can be properly re-ordered and delivered to the application.
Heavyweight – TCP requires three packets to set up a socket connection, before any user data can be sent. TCP handles reliability and congestion control.
Streaming – Data is read as a byte stream, no distinguishing indications are transmitted to signal message (segment) boundaries.
User Datagram Protocol is a simpler message-based connectionless protocol. Connectionless protocols do not set up a dedicated end-to-end connection. Communication is achieved by transmitting information in one direction from source to destination without verifying the readiness or state of the receiver.
Unreliable – When a UDP message is sent, it cannot be known if it will reach its destination; it could get lost along the way. There is no concept of acknowledgment, retransmission, or timeout.
Not ordered – If two messages are sent to the same recipient, the order in which they arrive cannot be predicted.
Lightweight – There is no ordering of messages, no tracking connections, etc. It is a small transport layer designed on top of IP.
Datagrams – Packets are sent individually and are checked for integrity only if they arrive. Packets have definite boundaries which are honored upon receipt, meaning a read operation at the receiver socket will yield an entire message as it was originally sent.
No congestion control – UDP itself does not avoid congestion. Congestion control measures must be implemented at the application level.
Broadcasts – being connectionless, UDP can broadcast - sent packets can be addressed to be receivable by all devices on the subnet.
Multicast – a multicast mode of operation is supported whereby a single datagram packet can be automatically routed without duplication to very large numbers of subscribers.
TCP is best suited to be used for applications that require high reliability where timing is less of a concern.
World Wide Web (HTTP, HTTPS)
Secure Shell (SSH)
File Transfer Protocol (FTP)
Email (SMTP, IMAP/POP)
UDP is best suited for applications that require speed and efficiency.
VPN tunneling
Streaming videos
Online games
Live broadcasts
Domain Name System (DNS)
Voice over Internet Protocol (VoIP)
Trivial File Transfer Protocol (TFTP)
ESEMPIO COMUNICAZIONE CLIENT/SERVER su UDP
Esempio applicazione client, io.checksound.networking.udp.QuoteClient, che invia una richiesta al server stabilendo una connessione di tipo UDP e il server risponde inviando dei messaggio di risposta:
packageio.checksound.networking.udp;importjava.io.*;importjava.net.*;importjava.util.*;publicclassQuoteClient {publicstaticvoidmain(String[] args) throwsIOException {if (args.length!=1) {System.out.println("Usage: java QuoteClient <hostname>");return; }// get a datagram socketDatagramSocket socket =newDatagramSocket();// send requestbyte[] buf =newbyte[256];InetAddress address =InetAddress.getByName(args[0]);DatagramPacket packet =newDatagramPacket(buf,buf.length, address,4445);socket.send(packet);// get response packet =newDatagramPacket(buf,buf.length);socket.receive(packet);// display responseString received =newString(packet.getData(),0,packet.getLength());System.out.println("Quote of the Moment: "+ received);socket.close(); }}
Le classi io.checksound.networking.udp.QuoteServer e io.checksound.networking.udp.QuoteServerThread implementano il server: il server al messaggio di richiesta del client invia un datagram di risposta contenente una frase: il server per sapere indirizzo e porta del client a cui rispondere, lo legge dal pacchetto che il client gli ha inviato:
packageio.checksound.networking.udp;importjava.io.*;importjava.net.*;importjava.util.*;publicclassQuoteServerThreadextendsThread {protectedDatagramSocket socket =null;protectedBufferedReader in =null;protectedboolean moreQuotes =true;publicQuoteServerThread() throwsIOException {this("QuoteServerThread"); }publicQuoteServerThread(String name) throwsIOException { super(name); socket =newDatagramSocket(4445);try { in =newBufferedReader(new FileReader("one-liners.txt")); } catch (FileNotFoundException e) {System.err.println("Could not open quote file. Serving time instead."); } }publicvoidrun() {while (moreQuotes) {try {byte[] buf =newbyte[256];// receive requestDatagramPacket packet =newDatagramPacket(buf,buf.length);socket.receive(packet);// figure out responseString dString =null;if (in ==null) dString =newDate().toString();else dString =getNextQuote(); buf =dString.getBytes();// send the response to the client at "address" and "port"InetAddress address =packet.getAddress();int port =packet.getPort(); packet =newDatagramPacket(buf,buf.length, address, port);socket.send(packet); } catch (IOException e) {e.printStackTrace(); moreQuotes =false; } }socket.close(); }protectedStringgetNextQuote() {String returnValue =null;try {if ((returnValue =in.readLine()) ==null) {in.close(); moreQuotes =false; returnValue ="No more quotes. Goodbye."; } } catch (IOException e) { returnValue ="IOException occurred in server."; }return returnValue; }}
Multicast
Finora sia nel caso TCP/IP che UDP/IP abbiamo visto un tipo di comunicazione di tipo unicast, cioè punto-punto, tra client e server: il client invia una richiesta al client e il server risponde alla richiesta.
Non abbiamo ancora visto servizi di tipo multicast, in cui un server, invia uno stesso messaggio contemporaneamente a più client.
Il protocollo UDP permette anche l'invio di messaggi di tipo multicast, cioè messaggi inviati contemporaneamente a più host che si mettono in ascolto su tipi particolari di indirizzi IP detti indirizzi di multicast.
NOTA: I dati sono trasportati sulla rete in tre semplici modi: Unicast, Broadcast e Multicast.
Per sintetizzare le differenze tra i tre modi:
Unicast: da una sorgente a una destinazione - One-to-One;
Broadcast: da una sorgente a tutte le possibili destinazioni - One-to-All;
Multicast: da una sorgente a molte destinazioni che dichiarano l'interesse a ricevere il traffico - One-to-Many;
Indirizzi di multicast sono indirizzi speciali; classe di indirizzamento D:
Class
First Octet Range
Network (N), Host (H)
Subnet Mask
No. of Networks
Hosts per Network
A
1-126
N.H.H.H.
255.0.0.0
126
16.777.214
B
128-191
N.N.H.H.
255.255.0.0
16.382
65.534
C
192-223
N.N.N.H.
255.255.255.0
2.097.150
254
D
224-239
Used for Multicasting.
E
240-254
Experimental; reserved for research purposes.
All'interno degli indirizzi multicast:
Start Address
End Address
Uses
224.0.0.0
224.0.0.255
Reserved for special "well known" multicast addresses
Esempio, sotto, di servizio in multicast: i client ricevono dati dal server che invia a indirizzo multicast (nell'esempio indirizzo IP 230.0.0.1 e porta . I client per ricevere i datagram inviati dal server, devono utilizzare una java.net.MulticastSocket ed eseguire una joinGroup(InetAddress mcastaddr) e quando non si desidera più ricevere più pacchetti bisogna eseguire il metodo leaveGroup(InetAddress mcastaddr).
Codice dell'applicazione server che invia messaggi:
packageio.checksound.networking.multicast;importjava.io.IOException;importjava.net.DatagramPacket;importjava.net.DatagramSocket;importjava.net.InetAddress;publicclassUDPMulticastServer {publicstaticvoidsendUDPMessage(String message,String ipAddress,int port) throwsIOException {DatagramSocket socket =newDatagramSocket();InetAddress group =InetAddress.getByName(ipAddress);byte[] msg =message.getBytes();DatagramPacket packet =newDatagramPacket(msg,msg.length, group, port);socket.send(packet);socket.close(); }publicstaticvoidmain(String[] args) throwsIOException {sendUDPMessage("This is a multicast messge","230.0.0.0",4321);sendUDPMessage("This is the second multicast messge","230.0.0.0",4321);sendUDPMessage("This is the third multicast messge","230.0.0.0",4321);sendUDPMessage("OK","230.0.0.0",4321); }}
Il codice dell'applicazione client che riceve i messaggi (vedi qui utilizzo di java.net.MulticastSocket):
Complichiamo ora l'esempio precedente: il client è in attesa dei messaggi inviati in multicast come prima, ma, una volta ricevuto il messaggio, invia al server un messaggio di ACKNOLEDGE; il server quindi oltre a inviare i messaggi ai client in multicast, aspetta le risposte di ACKNOLEDGE. Per ogni messaggio inviato dal server devono arrivare tanti messaggi di ACKNOLEDGE quanti sono client collegati.
Vediamo il codice del client, io.checksound.networking.multicast.UDPMulticastClient2, che riceve il messaggio multicast e invia l'ACKNOLEDGE per risposta al server:
packageio.checksound.networking.multicast;importjava.io.IOException;importjava.net.DatagramPacket;importjava.net.InetAddress;importjava.net.MulticastSocket;publicclassUDPMulticastClient2implementsRunnable {publicstaticvoidmain(String[] args) {Thread t =newThread(new UDPMulticastClient2());t.start(); }publicvoidreceiveUDPMessage(String ip,int port) throwsIOException {byte[] buffer =newbyte[1024];MulticastSocket socket =newMulticastSocket(port);InetAddress group =InetAddress.getByName(ip);socket.joinGroup(group);while (true) {System.out.println("Waiting for multicast message...");DatagramPacket packet =newDatagramPacket(buffer,buffer.length);socket.receive(packet);String msg =newString(packet.getData(),packet.getOffset(),packet.getLength());System.out.println("[Multicast UDP message received] >> "+ msg);InetAddress addressSender =packet.getAddress();int portSender =packet.getPort();System.out.println("FROM: "+ addressSender +", ON PORT: "+ portSender);// send acknoledge// get address and port to send acknoledge byte[] msgAckn ="client AKN".getBytes();DatagramPacket packetAcknoledge =newDatagramPacket(msgAckn,msgAckn.length, addressSender, portSender);socket.send(packetAcknoledge);if ("OK".equals(msg)) {System.out.println("No more message. Exiting : "+ msg);break; } }socket.leaveGroup(group);socket.close(); } @Overridepublicvoidrun() {try {receiveUDPMessage("230.0.0.0",4321); } catch (IOException ex) {ex.printStackTrace(); } }}
Il codice del server, io.checksound.networking.multicast.UDPMulticastServer2, che invia i messaggi e riceve gli ACKNOLEDGE:
packageio.checksound.networking.multicast;importjava.io.IOException;importjava.net.DatagramPacket;importjava.net.DatagramSocket;importjava.net.InetAddress;/** * Client give acknoledge they have received message. * * @author Massimo * */publicclassUDPMulticastServer2 { /** * Thread di lettura degli ACKNOLEDGE inviati dai client * @author Massimo * */privatestaticclassReaderThreadextendsThread {privateDatagramSocket socket =null;privatevolatileboolean isToStop =false;publicReaderThread(DatagramSocket socket) {this.socket= socket; }publicvoiddoStopThread() { isToStop =true; }publicvoidrun() {while(!isToStop) {byte[] buf =newbyte[256];DatagramPacket packet =newDatagramPacket(buf,buf.length);try {socket.receive(packet); } catch (IOException e) {if(!isToStop)e.printStackTrace();else {// exception OK because socket closedbreak; } }String received =newString(packet.getData(),0,packet.getLength());InetAddress inetAddress =packet.getAddress();int port =packet.getPort();System.out.println(" |---->>> RECV: "+ received +"| - FROM: "+ inetAddress +", on PORT: "+ port); }System.out.println("USCITA DAL THREAD LETTURA"); } } public static void sendUDPMessage(DatagramSocket socket, String message, String ipAddress, int port) throws IOException {
InetAddress group =InetAddress.getByName(ipAddress);byte[] msg =message.getBytes();DatagramPacket packet =newDatagramPacket(msg,msg.length, group, port); socket.send(packet); }privatestaticvoiddoSleep(int seconds) {try {Thread.sleep(seconds *1000); } catch (InterruptedException e) { } }publicstaticvoidmain(String[] args) throws IOException {DatagramSocket socket =newDatagramSocket();ReaderThread readerThread =newReaderThread(socket);readerThread.start();System.out.println("Invio primo messagio");sendUDPMessage(socket,"This is a multicast messge","230.0.0.0",4321);doSleep(3);System.out.println("Invio secondo messagio");sendUDPMessage(socket,"This is the second multicast messge","230.0.0.0",4321);doSleep(3);System.out.println("Invio terzo messagio");sendUDPMessage(socket,"This is the third multicast messge","230.0.0.0",4321);doSleep(3);System.out.println("Invio OK STOP");sendUDPMessage(socket,"OK","230.0.0.0",4321);doSleep(5);readerThread.doStopThread();System.out.println("In chiusura socket");socket.close(); }}
L'implementazione del client e server non è molto sofisticata (non ci sono molti controlli su gli ACKNOLEDGE ad esempio) per mantenere il codice semplice da capire e concentrarci sulla comprensione di come avviene lo scambio dei messaggi.
Utilizzo di java.net.MulticastSocket per lavorare con indirizzi di multicast.
L'applicazione ha un thread che è in ascolto sulla MulticastSocket dei pacchetti inviati dagli altri utenti della chat. Con il metodo receive(DatagramPacket p) di MulticastSocket (ereditato dalla superclasse DatagramSocket) riceve i pacchetti. Prima però deve essere invocato il metodo joinGroup(InetAddress mcastaddr) di MulticastSocket.
Il codice del thread che è in ascolto dei messaggi inviati dalle altre chat:
// metodo run del thread ReaderThread @Overridepublicvoidrun() {while (!GroupChat.finished) {byte[] buffer =newbyte[ReadThread.MAX_LEN];DatagramPacket datagram =newDatagramPacket(buffer,buffer.length, group, port);String message;try {socket.receive(datagram); message =newString(buffer,0,datagram.getLength(),"UTF-8");if (!message.startsWith(GroupChat.name))System.out.println(message); } catch (IOException e) {System.out.println("Socket closed!"); } } }
mentre nel main viene creata la MulticastSocket, eseguita la joinGroup ed il ciclo di invio dei messaggi digitati sulla tastiera tramite void send(DatagramPacket p):
InetAddress group =InetAddress.getByName("230.0.0.1");int port =3000;MulticastSocket socket =newMulticastSocket(port);// Since we are deployingsocket.setTimeToLive(0);// this on localhost only (For a subnet set it as 1)socket.joinGroup(group);while (true) {String message; message =sc.nextLine();if (message.equalsIgnoreCase(GroupChat.TERMINATE)) { finished =true;socket.leaveGroup(group);socket.close();break; } message = name +": "+ message;byte[] buffer =message.getBytes();DatagramPacket datagram =newDatagramPacket(buffer,buffer.length, group, port);socket.send(datagram);}
Qui il codice completo dell'applicazione io.checksound.networking.multicast.GroupChat:
io/checksound/networking/multicast/GroupChat.java
packageio.checksound.networking.multicast;importjava.net.*;importjava.io.*;importjava.util.*;publicclassGroupChat {privatestaticfinalString TERMINATE ="Exit";staticString name;staticvolatileboolean finished =false;publicstaticvoidmain(String[] args) {if (args.length!=2)System.out.println("Two arguments required: <multicast-host> <port-number>");else {try {InetAddress group =InetAddress.getByName(args[0]);int port =Integer.parseInt(args[1]);Scanner sc =newScanner(System.in);System.out.print("Enter your name: "); name =sc.nextLine();MulticastSocket socket =newMulticastSocket(port);// Since we are deployingsocket.setTimeToLive(0);// this on localhost only (For a subnet set it as 1)socket.joinGroup(group);Thread t =newThread(new ReadThread(socket, group, port));// Spawn a thread for reading messagest.start();// sent to the current groupSystem.out.println("Start typing messages...\n");while (true) {String message; message =sc.nextLine();if (message.equalsIgnoreCase(GroupChat.TERMINATE)) { finished =true;socket.leaveGroup(group);socket.close();break; } message = name +": "+ message;byte[] buffer =message.getBytes();DatagramPacket datagram =newDatagramPacket(buffer,buffer.length, group, port);socket.send(datagram); } } catch (SocketException se) {System.out.println("Error creating socket");se.printStackTrace(); } catch (IOException ie) {System.out.println("Error reading/writing from/to socket");ie.printStackTrace(); } } }}classReadThreadimplementsRunnable {privateMulticastSocket socket;privateInetAddress group;privateint port;privatestaticfinalint MAX_LEN =1000;ReadThread(MulticastSocket socket,InetAddress group,int port) {this.socket= socket;this.group= group;this.port= port; } @Overridepublicvoidrun() {while (!GroupChat.finished) {byte[] buffer =newbyte[ReadThread.MAX_LEN];DatagramPacket datagram =newDatagramPacket(buffer,buffer.length, group, port);String message;try {socket.receive(datagram); message =newString(buffer,0,datagram.getLength(),"UTF-8");if (!message.startsWith(GroupChat.name))System.out.println(message); } catch (IOException e) {System.out.println("Socket closed!"); } } }}
Esempio mandando in esecuzione il processo sull'indirizzo di multicast 230.0.0.1 alla porta 3000: