Lezione 11 · Canale 1 · venerdì 16 ottobre 2026

Connessioni di rete con i socket in Java

Progettazione del Software

Porte e socket

Un computer in rete è individuato da un nome. Per raggiungere un servizio serve anche un numero, la porta: lo stesso computer offre servizi diversi, ciascuno con il proprio protocollo, e a ogni protocollo corrisponde di solito una porta. Le pagine web usano la 80, l'invio di posta la 25. È come un numero di telefono con l'interno: il numero raggiunge l'organizzazione, l'interno il servizio.

Un socket è l'oggetto che rappresenta una connessione di rete: un canale di comunicazione fra il programma e un altro computer, da cui si legge e su cui si scrive. Le classi Socket e ServerSocket stanno in java.net e vanno importate.

  • Il client è il programma che avvia la connessione.
  • Il server è il programma che si mette in ascolto e accetta le connessioni dei client.

Il client crea il socket indicando il destinatario e la porta, per esempio new Socket("smtp.gmail.com", 25): il server di posta di Google. La creazione può fallire: l'indirizzo può non corrispondere a nessun computer, oppure il computer esiste ma nessun server ascolta su quella porta, o c'è un altro errore di comunicazione. Sono eccezioni, quindi main dichiara throws IOException. Alla fine il socket si chiude con close().

Dal socket alle righe

s.getInputStream() restituisce il flusso di ingresso: tutto ciò che l'altro computer invia arriva da qui. È un InputStream, cioè un flusso di byte. Per leggere testo servono due conversioni:

  • InputStreamReader decodifica i byte in caratteri: un carattere può occupare più byte, a seconda della codifica;
  • BufferedReader raccoglie i caratteri in righe, con readLine().
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;

public class Lettura {
    public static void main(String[] args)
            throws IOException {
        Socket s = new Socket("smtp.gmail.com", 25);
        InputStream is = s.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);
        String linea = br.readLine();
        System.out.println(linea);
        s.close();
    }
}

Ogni oggetto è costruito sul precedente, come per i file: is è il flusso di byte del socket, isr lo legge come caratteri, br raccoglie in righe i caratteri di isr. readLine() chiede caratteri a isr finché la riga non è completa, e questo li ricava dai byte del socket. Cambia solo la sorgente: un canale di rete invece di un file. Il programma stampa la prima riga che il server invia appena la connessione è aperta. Al posto del BufferedReader si può usare uno Scanner costruito sul flusso di ingresso, con nextLine().

Per scrivere serve il flusso di uscita, s.getOutputStream(), un OutputStream di byte. Si converte come in lettura: OutputStreamWriter per i caratteri, PrintWriter per scrivere stringhe con write.

LivelloIn ingressoIn uscita
bytegetInputStream()getOutputStream()
caratteriInputStreamReaderOutputStreamWriter
righeBufferedReader: readLine()PrintWriter: write()

Come per i file, write non va a capo e non spedisce niente finché non si chiama flush(): senza, i dati restano nel computer. In rete conta anche il terminatore, perché i protocolli sono precisi: in SMTP una riga finisce con i due caratteri \r\n, e vanno scritti esattamente quelli.

Il canale è bidirezionale: un flusso per i dati ricevuti, uno per i dati inviati, e ciascuna parte può leggere e scrivere.

Un client SMTP

SMTP, Simple Mail Transfer Protocol, è il protocollo con cui si invia la posta. Alla connessione il server manda una riga; poi il client invia un comando e il server risponde. Ogni comando comincia con una parola di quattro lettere: HELO è il saluto, con il nome di chi si presenta; QUIT chiude lo scambio. I comandi per mittente, destinatario e testo del messaggio sono dello stesso tipo. Ai due comandi usati qui il server risponde con una sola riga.

Le righe inviate si stampano con >>> davanti, per distinguerle da quelle ricevute. D'ora in avanti i frammenti omettono import e main, che dichiara throws IOException.

Socket s = new Socket("smtp.gmail.com", 25);
InputStream is = s.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
OutputStream os = s.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os);
PrintWriter pr = new PrintWriter(osw);

String linea = br.readLine();
System.out.println(linea);

linea = "HELO prova";
System.out.println(">>> " + linea);
pr.write(linea + "\r\n");
pr.flush();
linea = br.readLine();
System.out.println(linea);

linea = "QUIT";
System.out.println(">>> " + linea);
pr.write(linea + "\r\n");
pr.flush();
linea = br.readLine();
System.out.println(linea);
s.close();

Il server risponde a QUIT e se ne va: la connessione finisce, e il client la chiude a sua volta.

Da SMTP a HTTP

HTTP è il protocollo delle pagine web. La struttura del programma non cambia: si crea il socket, si aprono i due flussi, poi si segue il protocollo. Cambiano i messaggi e chi comincia.

SMTPHTTP
Chi parla per primoil serveril client
Scambiocomandi e risposte, uno dopo l'altrouna richiesta di più righe, una risposta di più righe

Il client invia tre righe: la richiesta, l'indirizzo del sito e una riga vuota, che chiude la richiesta. Le può scrivere insieme o una alla volta, purché poi chiami flush(). La risposta ha più righe, quindi si legge con il ciclo dei file.

Socket s = new Socket("google.com", 80);
InputStream is = s.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
OutputStream os = s.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os);
PrintWriter pr = new PrintWriter(osw);

pr.write("GET / HTTP/1.1\r\n");
pr.write("Host: google.com\r\n");
pr.write("\r\n");
pr.flush();

String linea;
while ((linea = br.readLine()) != null)
    System.out.println(linea);
s.close();

Il server risponde con intestazioni e un documento: qui una ridirezione, che rimanda a un'altra pagina. Poi però non chiude la connessione, e readLine() resta in attesa della riga successiva: il ciclo finisce solo se l'altro lato chiude, e il programma va fermato a mano. Per fermarsi da soli bisognerebbe leggere solo il documento, la cui lunghezza in byte è scritta fra le intestazioni della risposta.

Il server: ServerSocket e accept

Finora il programma era il client: è lui ad aprire la connessione. Un server invece si mette in ascolto, e sono gli altri a collegarsi.

  1. new ServerSocket(8080) dichiara che il programma ascolta sulla porta 8080. Da solo non fa succedere nulla.
  2. accept() chiede la prossima connessione. Il metodo blocca il programma finché un client non si collega, poi restituisce un Socket.
ServerSocket ss = new ServerSocket(8080);
System.out.println("attesa");
Socket s = ss.accept();
System.out.println("terminato");

Senza accept() il programma finisce subito. Con accept() stampa attesa e si ferma. Basta avviare un secondo programma, un client che si collega a localhost, il computer stesso, sulla stessa porta:

Socket s = new Socket("localhost", 8080);
s.close();

Appena il client si collega, l'attesa finisce e il server stampa terminato. Il server va avviato per primo: senza nessuno in ascolto, il client fallisce.

Dopo l'accept() il canale è lo stesso da entrambi i lati: ognuno ha un socket con il suo flusso di ingresso e il suo flusso di uscita, e ciò che uno scrive l'altro lo legge. Le istruzioni per leggere e scrivere sono le stesse del client. Cambia solo il protocollo da seguire.

Un server e un client che dialogano

Il protocollo dell'esempio: il client invia una riga, il server la stampa e risponde con la stessa riga ripetuta due volte.

ServerSocket ss = new ServerSocket(8080);
Socket s = ss.accept();
InputStream is = s.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
OutputStream os = s.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os);
PrintWriter pr = new PrintWriter(osw);

String linea = br.readLine();
System.out.println(linea);
pr.write(linea + " " + linea + "\r\n");
pr.flush();
s.close();
ss.close();
Socket s = new Socket("localhost", 8080);
InputStream is = s.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
OutputStream os = s.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os);
PrintWriter pr = new PrintWriter(osw);

String linea = "ciao";
System.out.println(">>> " + linea);
pr.write(linea + "\r\n");
pr.flush();
linea = br.readLine();
System.out.println(linea);
s.close();

Avviato prima il server, e poi il client, nella console del server compare ciao, in quella del client >>> ciao e ciao ciao. I due programmi differiscono solo per l'inizio: il server si mette prima in attesa. Dopo, il codice è lo stesso, e in entrambi il socket è s.

Un server web

Un server web riceve la richiesta di un browser. Legge righe finché non arriva una riga vuota, che chiude la richiesta, le stampa, poi risponde. Il confronto con la riga vuota si fa con equals, perché le stringhe sono oggetti.

ServerSocket ss = new ServerSocket(8080);
Socket s = ss.accept();
InputStream is = s.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
OutputStream os = s.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os);
PrintWriter pr = new PrintWriter(osw);

String linea;
do {
    linea = br.readLine();
    System.out.println(linea);
} while (!linea.equals(""));

pr.write("HTTP/1.1 200 OK\r\n"
        + "Content-Type: text/plain\r\n"
        + "\r\n"
        + "Ciao dal server\r\n");
pr.flush();
s.close();
ss.close();

La risposta qui è la più semplice possibile: la riga di stato, un'intestazione sul tipo del documento, la riga vuota e il documento. Senza lunghezza dichiarata, il documento finisce quando il server chiude la connessione.

Con il server in ascolto, il browser si collega a localhost:8080: sulla console compaiono le righe della richiesta, e nella pagina la risposta. Senza il server avviato la connessione fallisce.

Il server però termina dopo una sola connessione: ricaricando la pagina non risponde più. Basterebbe tornare ad accept() per attendere la successiva, ma resterebbe un server a una connessione e a un solo thread: finché ne gestisce una non può servire le altre. Servirne più insieme richiede i thread.

Formulario

Client

  • new Socket(indirizzo, porta)
  • getInputStream()
  • getOutputStream()
  • close()

Server

  • new ServerSocket(porta)
  • accept(): attende, poi dà un Socket
  • si avvia per primo

Righe in ingresso

  • InputStreamReader
  • BufferedReader
  • readLine()

Righe in uscita

  • OutputStreamWriter
  • PrintWriter
  • write(...) e flush()

Protocolli

  • SMTP: il server parla per primo
  • HTTP: il client parla per primo
  • riga SMTP: finisce con \r\n
  • richiesta HTTP: finisce con una riga vuota