Ti sei mai chiesto come funziona lo streaming? Oppure cosa succede dopo che hai inviato un messaggio su WhatsApp? Tutto parte dai Socket!
Socket significa presa e prima di tutto è un concetto: Prendi due prese, collegale con un cavo e potranno comunicare tra loro. La prima implementazione di Socket nasce negli anni ‘80 come API (Application Programming Interface) per trasferire dati tra processi nei sistemi operativi Unix. Il concetto si è poi rivelato essere talmente buono e innovativo, che a queste API sono state aggiunte altre famiglie di socket:
In base alle caratteristiche del software da realizzare possiamo scegliere il tipo di socket:
Il Socket quindi è un oggetto software che permette la comunicazione tra processi locali o host remoti.
In questa guida ci concentreremo sui socket AF_INET.
Nei Socket AF_INET lo Stream Socket utilizza il protocollo TCP, mentre il Datagram Socket usa UDP.
Per identificare un Socket è necessario un indirizzo, chiamato Socket Address. Nel caso della famiglia AF_INET, il Socket Address è composto da due elementi:
La comunicazione con UDP dei Datagram Socket è molto semplice: un socket (client) invia una richiesta ad un altro socket (server) e questo può ritornargli una risposta come no.
Scriviamo un semplice programma per mostrare l’utilizzo della Classe DatagramSocket.
Abbiamo un server che ricevuta una connessione risponde con l’ora esatta.
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class OraEsattaServer {
private int porta;
private DatagramSocket socket;
public OraEsattaServer(int porta) throws SocketException{
//Apro il socket sulla porta specificata
this.porta = porta;
this.socket = new DatagramSocket(porta);
System.out.println("Server avviato sulla porta " + porta);
}
public void receive(){
try{
// Ricevo una richiesta (non leggo i dati ricevuti, non mi servono)
DatagramPacket richiesta = new DatagramPacket(new byte[256], 256);
socket.receive(richiesta);
// Mi prendo l'ora esatta, la trasformo in string e poi in array di byte per poterla inviare
DateTimeFormatter formatoOra = DateTimeFormatter.ofPattern("HH:mm:ss");
String ora = "L'ora esatta è: " + formatoOra.format(LocalDateTime.now());
byte[] bufferOra = ora.getBytes();
// Mi prendo ip e porta del client dal pacchetto che ho ricevuto
InetAddress ipClient = richiesta.getAddress();
int portaClient = richiesta.getPort();
// Invio la risposta al client
DatagramPacket risposta = new DatagramPacket(bufferOra, bufferOra.length, ipClient, portaClient);
socket.send(risposta);
}
catch(IOException e){
System.out.println("Errore nella risposta: "+ e);
}
}
public static void main(String[] args) {
int serverPort = 8000;
OraEsattaServer server;
try{
server = new OraEsattaServer(serverPort);
}
catch(SocketException e){
System.out.println("Errore all'avvio: "+ e);
return;
}
System.out.println("Premere Ctrl + C per fermare il server");
while(true){
server.receive();
}
}
}
Abbiamo poi un client che gli invia una richiesta:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
public class OraEsattaClient {
public static void main(String[] args){
int serverPort = 8000;
String serverIP = "127.0.0.1";
try{
// Mi prendo l'IP dalla string serverIP e apro un socket
InetAddress serverAddress = InetAddress.getByName(serverIP);
DatagramSocket socket = new DatagramSocket();
// Creo e invio una richiesta (vuota, non devo inviare nessun dato)
DatagramPacket richiesta = new DatagramPacket(new byte[256], 256, serverAddress, serverPort);
socket.send(richiesta);
// Ricevo la risposta dal server
byte[] bufferRisposta = new byte[256];
DatagramPacket risposta = new DatagramPacket(bufferRisposta, bufferRisposta.length);
socket.receive(risposta);
// Trasformo in string ala risposta
String ora = new String(bufferRisposta, 0, risposta.getLength());
// Stampo l'ora esatta e chiudo il client
System.out.println(ora);
socket.close();
}
catch(SocketTimeoutException e){
System.out.println("Timeout: "+ e);
}
catch(IOException e){
System.out.println("Errore nell' elaborazione della risposta: "+ e);
}
}
}
Avviamo prima il server, poi il client; Da quest’ultimo otterremo il seguente output:
L'ora esatta è: 21:18:28
La comunicazione con gli Stream Socket è un po’ più complessa: siccome il protocollo TCP è orientato e confermato dal lato server avremo un socket per ogni connessione stabilita con un client.
Per stabilire la connessione il client farà una richiesta alla porta nota del server, il socket in ascolto delegherà la richiesta ad un nuovo socket, il quale effettuerà un three way handshake con il client e risponderà alle sue richieste. Il socket che si è liberato potrà tornare in ascolto di nuovi client. In questo modo il server può gestire più richieste contemporaneamente.
Scriviamo un programma con le classi ServerSocket e Socket. In base al comando che invieremo sarà possibile ottenere la data o l’ora.
Abbiamo un server con un ServerSocket in ascolto. Quando un client tenta di connettersi il ServerSocket istanzierà un Socket che continuerà la comunicazione su un thread separato.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DataOraServer {
private int porta;
private ServerSocket serverSocket;
public DataOraServer(int porta) throws IOException{
//Apro il socket sulla porta specificata
this.porta = porta;
this.serverSocket = new ServerSocket(porta);
System.out.println("Server avviato sulla porta " + porta);
}
public void listen(){
try{
// Ascolto in attesa di una richiesta di connessione
Socket delegato = serverSocket.accept();
// Avvio la comunicazione con il client su un nuovo thread, per poter accettare altre richieste
Thread t = new Thread(() -> gestisciClient(delegato));
t.start();
}
catch(IOException e){
System.out.println("Errore nella connessione con il client: "+ e);
}
}
private void gestisciClient(Socket socket){
try{
// Mi prendo gli stream per ricevere ed inviare dati al client
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
DateTimeFormatter formatoOra = DateTimeFormatter.ofPattern("HH:mm:ss");
DateTimeFormatter formatoData = DateTimeFormatter.ofPattern("dd/MM/yyyy");
//rimango in ascolto del client finchè è connesso
while(socket.isConnected()){
String comando = in.readLine();
switch(comando){
case "data":
out.println("Data: " + formatoData.format(LocalDateTime.now()));
break;
case "ora":
out.println("Ora: " + formatoOra.format(LocalDateTime.now()));
break;
default:
out.println("Comando non riconosciuto");
break;
}
}
}
catch(IOException e){
System.out.println("Errore nella comunicazione con il client: "+ e);
}
}
public static void main(String[] args) {
int serverPort = 8000;
DataOraServer server;
try{
server = new DataOraServer(serverPort);
}
catch(IOException e){
System.out.println("Errore all'avvio: "+ e);
return;
}
System.out.println("Premere Ctrl + C per fermare il server");
while(true){
server.listen();
}
}
}
Abbiamo un client che chiede la data e successivamente l’ora:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class DataOraClient {
public static void main(String[] args){
int serverPort = 8000;
String serverIP = "127.0.0.1";
try{
// Apro un socket e mi connetto al server
Socket socket = new Socket(serverIP, serverPort);
// Mi prendo gli stream per ricevere ed inviare dati al server
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//chiedo la data
out.println("data");
// Ricevo la risposta dal server e la stampo
String risposta = in.readLine();
System.out.println(risposta);
//chiedo l'ora
out.println("ora");
// Ricevo la risposta dal server e la stampo
risposta = in.readLine();
System.out.println(risposta);
//Chiudo la connessione con il server
socket.close();
}
catch(SocketTimeoutException e){
System.out.println("Timeout: "+ e);
}
catch(IOException e){
System.out.println("Errore nell' elaborazione della risposta: "+ e);
}
}
}
Avviamo prima il server, poi il client; Da quest’ultimo otterremo il seguente output:
Data: 30/09/2020
Ora: 21:35:11