Messaggi datagram e invio multicast

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.

DIFFERENZE TRA UDP e TCP

Da https://en.wikipedia.org/wiki/User_Datagram_Protocol

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.

Vedi anche: https://www.comparitech.com/blog/vpn-privacy/udp-vs-tcp-ip/

Utilizzo

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:

package io.checksound.networking.udp;

import java.io.*;
import java.net.*;
import java.util.*;

public class QuoteClient {
	public static void main(String[] args) throws IOException {

		if (args.length != 1) {
			System.out.println("Usage: java QuoteClient <hostname>");
			return;
		}

		// get a datagram socket
		DatagramSocket socket = new DatagramSocket();

		// send request
		byte[] buf = new byte[256];
		InetAddress address = InetAddress.getByName(args[0]);
		DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 4445);
		socket.send(packet);

		// get response
		packet = new DatagramPacket(buf, buf.length);
		socket.receive(packet);

		// display response
		String received = new String(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:

package io.checksound.networking.udp;

import java.io.*;
 
public class QuoteServer {
    public static void main(String[] args) throws IOException {
        new QuoteServerThread().start();
    }
}
package io.checksound.networking.udp;

import java.io.*;
import java.net.*;
import java.util.*;

public class QuoteServerThread extends Thread {

	protected DatagramSocket socket = null;
	protected BufferedReader in = null;
	protected boolean moreQuotes = true;

	public QuoteServerThread() throws IOException {
		this("QuoteServerThread");
	}

	public QuoteServerThread(String name) throws IOException {
		super(name);
		socket = new DatagramSocket(4445);

		try {
			in = new BufferedReader(new FileReader("one-liners.txt"));
		} catch (FileNotFoundException e) {
			System.err.println("Could not open quote file. Serving time instead.");
		}
	}

	public void run() {

		while (moreQuotes) {
			try {
				byte[] buf = new byte[256];

				// receive request
				DatagramPacket packet = new DatagramPacket(buf, buf.length);
				socket.receive(packet);

				// figure out response
				String dString = null;
				if (in == null)
					dString = new Date().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 = new DatagramPacket(buf, buf.length, address, port);
				socket.send(packet);
			} catch (IOException e) {
				e.printStackTrace();
				moreQuotes = false;
			}
		}
		socket.close();
	}

	protected String getNextQuote() {
		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

224.0.1.0

238.255.255.255

Globally scoped (Internet-wide) multicast address

239.0.0.0

239.255.255.255

Administratively scoped (local) multicast addresses

Reference: http://www.tcpipguide.com/free/t_IPMulticastAddressing.htm

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:

io/checksound/networking/multicast/UDPMulticastServer.java
package io.checksound.networking.multicast;

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

public class UDPMulticastServer {

	public static void sendUDPMessage(String message, String ipAddress, int port) throws IOException {
		DatagramSocket socket = new DatagramSocket();
		InetAddress group = InetAddress.getByName(ipAddress);
		byte[] msg = message.getBytes();
		DatagramPacket packet = new DatagramPacket(msg, msg.length, group, port);
		socket.send(packet);
		socket.close();
	}

	public static void main(String[] args) throws IOException {
		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):

io/checksound/networking/multicast/UDPMulticastClient.java
package io.checksound.networking.multicast;

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

public class UDPMulticastClient implements Runnable {

	public static void main(String[] args) {
		Thread t = new Thread(new UDPMulticastClient());
		t.start();
	}

	public void receiveUDPMessage(String ip, int port) throws IOException {
		byte[] buffer = new byte[1024];
		MulticastSocket socket = new MulticastSocket(port);
		InetAddress group = InetAddress.getByName(ip);
		socket.joinGroup(group);
		while (true) {
			System.out.println("Waiting for multicast message...");
			DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
			socket.receive(packet);
			String msg = new String(packet.getData(), packet.getOffset(), packet.getLength());
			System.out.println("[Multicast UDP message received] >> " + msg);
			if ("OK".equals(msg)) {
				System.out.println("No more message. Exiting : " + msg);
				break;
			}
		}
		socket.leaveGroup(group);
		socket.close();
	}

	@Override
	public void run() {
		try {
			receiveUDPMessage("230.0.0.0", 4321);
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}
}

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:

package io.checksound.networking.multicast;

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

public class UDPMulticastClient2 implements Runnable {

	public static void main(String[] args) {
		Thread t = new Thread(new UDPMulticastClient2());
		t.start();
	}

	public void receiveUDPMessage(String ip, int port) throws IOException {
		byte[] buffer = new byte[1024];
		MulticastSocket socket = new MulticastSocket(port);
		InetAddress group = InetAddress.getByName(ip);
		socket.joinGroup(group);
		
		while (true) {
			System.out.println("Waiting for multicast message...");
			DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
			socket.receive(packet);
			String msg = new String(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 = new DatagramPacket(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();
	}

	@Override
	public void run() {
		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:

package io.checksound.networking.multicast;

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

/**
 * Client give acknoledge they have received message.
 * 
 * @author Massimo
 *
 */
public class UDPMulticastServer2 {
	
	/**
	 * Thread di lettura degli ACKNOLEDGE inviati dai client
	 * @author Massimo
	 *
	 */
	private static class ReaderThread extends Thread {
		
		private DatagramSocket socket = null;
		private volatile boolean isToStop = false;
		
		public ReaderThread(DatagramSocket socket) {
			this.socket = socket;
		}
		
		public void doStopThread() {
			isToStop = true;
		}
		
		public void run() {
			
			while(!isToStop) {
				byte[] buf = new byte[256];
				DatagramPacket packet = new DatagramPacket(buf, buf.length);
				try {
					socket.receive(packet);
				} catch (IOException e) {
					if(!isToStop)
						e.printStackTrace();
					else {
						// exception OK because socket closed
						break;
					}
				}
				
				String received = new String(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 = new DatagramPacket(msg, msg.length, group, port);
		socket.send(packet);
	}

	private static void doSleep(int seconds) {
		try {
			Thread.sleep(seconds * 1000);
		} catch (InterruptedException e) {
			
		}
	}
	public static void main(String[] args) throws IOException {
		DatagramSocket socket = new DatagramSocket();
		
		ReaderThread readerThread = new ReaderThread(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.

UNA SEMPLICE APPLICAZIONE DI TIPO CHAT IN JAVA

Da: https://www.geeksforgeeks.org/a-group-chat-application-in-java/

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
  @Override
	public void run() {
		while (!GroupChat.finished) {
			byte[] buffer = new byte[ReadThread.MAX_LEN];
			DatagramPacket datagram = new DatagramPacket(buffer, buffer.length, group, port);
			String message;
			try {
				socket.receive(datagram);
				message = new String(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 = new MulticastSocket(port);

// Since we are deploying
socket.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 = new DatagramPacket(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
package io.checksound.networking.multicast;

import java.net.*;
import java.io.*;
import java.util.*;

public class GroupChat {
	private static final String TERMINATE = "Exit";
	static String name;
	static volatile boolean finished = false;

	public static void main(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 = new Scanner(System.in);
				System.out.print("Enter your name: ");
				name = sc.nextLine();
				MulticastSocket socket = new MulticastSocket(port);

				// Since we are deploying
				socket.setTimeToLive(0);
				// this on localhost only (For a subnet set it as 1)

				socket.joinGroup(group);
				Thread t = new Thread(new ReadThread(socket, group, port));

				// Spawn a thread for reading messages
				t.start();

				// sent to the current group
				System.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 = new DatagramPacket(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();
			}
		}
	}
}

class ReadThread implements Runnable {
	private MulticastSocket socket;
	private InetAddress group;
	private int port;
	private static final int MAX_LEN = 1000;

	ReadThread(MulticastSocket socket, InetAddress group, int port) {
		this.socket = socket;
		this.group = group;
		this.port = port;
	}

	@Override
	public void run() {
		while (!GroupChat.finished) {
			byte[] buffer = new byte[ReadThread.MAX_LEN];
			DatagramPacket datagram = new DatagramPacket(buffer, buffer.length, group, port);
			String message;
			try {
				socket.receive(datagram);
				message = new String(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:

java io.checksound.networking.multicast.GroupChat 230.0.0.1 3000

CODICE ESEMPI

Last updated