In questo articolo, Programmazione di rete con i SOCKET, esploreremo i concetti fondamentali della programmazione con i socket, con un focus su tipologie, modelli di comunicazione, e una guida pratica all’implementazione in linguaggi come Java. Partiremo dai principi base per arrivare a una panoramica sugli stream socket e datagram socket, passando per le API storiche come quelle di Berkeley e i paradigmi di programmazione client-server. Inoltre, toccheremo temi come l’identificazione tramite IP e porte logiche, l’uso del multicast e i protocolli più utilizzati come TCP e UDP.
Indice dei contenuti
Introduzione
La programmazione di rete rappresenta una delle discipline fondamentali dell’informatica moderna, grazie al ruolo cruciale delle comunicazioni tra dispositivi. I socket sono una delle tecnologie principali alla base della programmazione di rete, permettendo la creazione di applicazioni che comunicano attraverso reti LAN e WAN. Dai videogiochi online alla messaggistica istantanea, i socket sono alla base di numerose applicazioni che utilizziamo quotidianamente.
Capitolo 1: Introduzione alla Programmazione di Rete e ai Socket
La programmazione di rete, o Networking Programming, è il processo di sviluppo di applicazioni software che comunicano tra loro utilizzando reti come LAN (Local Area Network) o WAN (Wide Area Network). Questa disciplina consente a dispositivi su computer distinti di scambiare dati in modo efficiente, aprendo le porte a innumerevoli applicazioni, dai sistemi di messaggistica istantanea ai servizi cloud.
API : una panoramica
I moderni sistemi operativi, come Windows e Linux, forniscono un’Application Programming Interface (API) che si colloca tra i protocolli di trasporto TCP (Transmission Control Protocol) o UDP (User Datagram Protocol) e i livelli superiori della comunicazione (sessione, presentazione e applicazione). Le API offerte da questi sistemi operativi consentono agli sviluppatori di interfacciarsi direttamente con i protocolli di rete.
Inizialmente, queste API erano implementate principalmente attraverso funzioni in linguaggio C, ma oggi la maggior parte dei linguaggi di programmazione, come Java o Python, offre librerie e classi che semplificano l’accesso a tali funzionalità. Quando si utilizza direttamente questa interfaccia, la programmazione di rete è definita programmazione a livello di socket.
Programmazione a Livello Applicativo
Con la diffusione del modello client/server, la programmazione di rete si è orientata anche verso le API applicative che sfruttano protocolli come l’HTTP. Questo approccio, spesso definito programmazione a livello applicativo, semplifica ulteriormente la comunicazione e il trasferimento di dati tra macchine.
Berkeley Socket API e le Alternative
Le API per la programmazione a livello di socket sono nate nel 1981 presso l’Università di Berkeley, in California, all’interno del progetto Berkeley Software Distribution (BSD), una variante del sistema operativo Unix. Questo modello è diventato noto come Berkeley socket API.
Sistemi operativi come Windows e Linux offrono supporto simile:
- Windows utilizza una variante chiamata WinSock, molto simile alla Berkeley API.
- Linux, invece, implementa direttamente la Berkeley socket API.
Entrambe le API adottano il paradigma client/server e supportano due tipi principali di servizio a livello di trasporto:
- TCP (affidabile e orientato alla connessione).
- UDP (non affidabile e basato su datagrammi).
Capitolo 2: definizione di Socket
Affinchè un processo presente (mittente) su un host possa inviare dati ad un qualsiasi altro host (destinatario) in una rete è necessario che il mittente identifichi il destinatario in modo univoco
Ogni PC ha una porta fisica di comunicazione ma necessita di ricevere ed inviare dati attraverso applicazioni diverse. Come facciamo ad identificare un servizio, un’applicazione in modo univoco?
Attraverso le Porte logiche di comunicazione. Le porte logiche sono specificate attraverso 2 byte (possiamo avere dunque 65536 porte numerate da 0 a 65535). Ciascuna di queste porte identifica un canale di comunicazione.
Le prime 1024 porte si chiamano Well Known Ports. Possono essere usate solo dai Server e riservate a servizi o applicazioni specifiche (HTTP, FTP, SMTP,..)
Le porte dalla 1024 alla 49151 si chiamano Registered Ports sono riservate ad alcuni servizi e usate dai client
Le ultime porte sono dette Dynamic and/or Private Ports e sono usate dai processi per essere assegnate dinamicamente

L’identificazione di un servizio avviene dunque combinando indirizzo IP e porta logica.
La notazione è < Indirizzo IP: numero della porta logica >
Il meccanismo di identificazione univoca associato ad un processo in esecuzione su un host attraverso la coppia
< Indirizzo IP: numero della porta logica > è chiamato SOCKET
Un host che vuole comunicare in rete con un altro host invia il messaggio attraverso il proprio socket (socket del mittente) che rappresenta l’interfaccia verso l’esterno. Occorre poi un “oggetto” che trasferirà il messaggio al destinatario (evidentemente al socket del destinatario) L’oggetto di cui parliamo è il protocollo utilizzato. Pertanto, se attraverso il socket identifichiamo in modo univoco un processo su un host, in che modo identifichiamo in modo univoco ogni singola connessione?
Ogni connessione è identificata univocamente da una struttura chiamata ASSOCIATION costituita da una quintupla di elementi:
- IP del mittente
- numero di porta del mittente
- IP del destinatario
- numero di porta del destinatario
- protocollo utilizzato
Capitolo 3: Famiglie di Socket
Stream Socket (SOCK_STREAM)
Gli stream socket (SOCK_STREAM) sono fondamentali per la creazione di connessioni sequenziali e affidabili tra processi su reti. Questo tipo di socket consente una comunicazione full-duplex (bidirezionale simultanea) ed è caratterizzato da:
- Connessione sequenziale: i dati vengono trasmessi in ordine, rispettando la sequenza di invio.
- Asimmetria: le velocità di trasmissione tra i due estremi possono differire.
- Affidabilità: garantisce che i dati arrivino correttamente e senza duplicati.
- Stream di byte a lunghezza variabile: i dati vengono gestiti come flussi continui di byte.
Operativamente, ogni processo coinvolto nella comunicazione crea un endpoint per lo scambio di dati. Questo processo si realizza invocando la primitiva socket()
in C, o creando un oggetto socket in Java. In questa guida, faremo riferimento principalmente alle implementazioni in Java.
Ciclo Operativo degli Stream Socket
-
Lato Server:
- Il server si mette in ascolto su una porta specifica (listening socket), in attesa di connessioni.
- Quando riceve una richiesta, utilizza la primitiva
accept()
per generare un nuovo socket dedicato alla connessione con il client.
-
Lato Client:
- Il client si mette in coda sul socket del server.
- Una volta accettata la connessione dal server, il client effettua automaticamente il binding alla propria porta locale.
Ruoli di Server e Client
Il processo server ha un controllo maggiore rispetto al client, in quanto:
- È responsabile della creazione iniziale del socket e della gestione delle connessioni multiple.
- Può essere associato a un solo socket per porta specifica. Il client, invece, deve conoscere l’indirizzo IP e la porta del server per iniziare una connessione. Il server acquisisce le informazioni del client, inclusi l’IP e la porta, solo al momento della connessione.
Comunicazione
Dopo l’accettazione della richiesta, il server crea un “canale virtuale” tra il client e un nuovo socket, lasciando il socket principale libero per ulteriori richieste. Durante la comunicazione, i due processi utilizzano:
- La funzione
read()
per leggere i dati. - La funzione
write()
per scrivere i dati.
La comunicazione continua fino a quando uno dei due processi chiude il canale tramite la funzione close()
.
Datagram Socket (SOCK_DGRAM)
I datagram socket (SOCK_DGRAM) permettono di realizzare una comunicazione basata su pacchetti di dati, senza stabilire una connessione diretta tra le parti coinvolte. Questo tipo di socket è particolarmente utile per situazioni in cui la rapidità di invio è preferita all’affidabilità garantita, come nel caso del protocollo UDP (User Datagram Protocol). Il modello supporta una comunicazione di tipo “molti a molti”, dove:
- Un singolo socket può inviare dati a diverse destinazioni.
- Può ricevere dati da più sorgenti.
A differenza degli stream socket, i datagram socket non garantiscono:
- L’ordine dei pacchetti.
- La consegna affidabile dei dati (potrebbero esserci perdite).
Caratteristiche Operative
Ogni processo che utilizza un datagram socket crea un endpoint richiamando la primitiva che inizializza il socket. Successivamente, il processo può:
- Inviare dati a un destinatario specifico.
- Ricevere dati da una o più sorgenti, utilizzando le primitive appropriate.
Per implementare questa comunicazione, si utilizzano funzioni e primitive disponibili nei linguaggi di programmazione. Ad esempio:
- In Java, tramite i metodi
send()
ereceive()
. - In C, con le primitive
recvfrom()
esendto()
.
Ciclo di Comunicazione
-
Lato Server: il server si mette in attesa di dati utilizzando la primitiva
receive()
. Una volta ricevuti i dati, può elaborare e inviare una risposta tramite la primitivasend()
. -
Lato Client: il client invia i dati al server utilizzando
send()
e può attendere una risposta dallo stesso server conreceive()
.
Questa modalità di scambio dati offre una grande flessibilità, in quanto non richiede una connessione continua, ma ha il limite di non garantire né l’ordine né la certezza dell’arrivo dei pacchetti.
I datagram socket sono utili in molte applicazioni, come:
- Gaming online, dove la velocità è essenziale e la perdita di qualche pacchetto non compromette l’esperienza.
- Streaming multimediale, in cui i piccoli ritardi o perdite di dati non influenzano in modo significativo il risultato.
Conclusioni
Abbiamo esplorato i fondamenti della programmazione di rete con i socket, analizzando il loro funzionamento, le tipologie principali e i paradigmi di comunicazione client-server. I socket rappresentano una tecnologia essenziale per la comunicazione tra processi su macchine distinte, offrendo la flessibilità e la potenza necessarie per sviluppare applicazioni moderne.
Questo articolo è solo un punto di partenza. Nei prossimi approfondimenti ci concentreremo sull’implementazione pratica dei socket in Java, creando esempi concreti e applicazioni reali per mettere in pratica i concetti appresi. Inoltre, forniremo una guida dettagliata su come configurare correttamente il tuo ambiente di lavoro con Visual Studio Code e Java, per prepararti a sviluppare codice in maniera efficiente e produttiva.
Resta connesso per scoprire come sfruttare al massimo la programmazione di rete e i socket nel mondo Java!
Lascia un commento