W3docs

Java ServerSocket

Accetta connessioni TCP in Java con ServerSocket e costruisci un server semplice.

L'estremità client di una connessione TCP è Socket. L'estremità server è java.net.ServerSocket: si associa a una porta, ascolta le connessioni in arrivo e ti restituisce un normale Socket per ogni client che si connette. Un singolo ServerSocket accetta molti client; ogni chiamata ad accept() restituisce una connessione separata.

Questo capitolo spiega come associarsi e accettare connessioni, come servire più client contemporaneamente, il ruolo del backlog di connessione e la disciplina del ciclo di vita necessaria per un server a lungo termine.

Bind, accept, serve

ServerSocket server = new ServerSocket(8080);   // bind to port 8080
while (true) {
    Socket client = server.accept();            // blocks until a client connects
    handle(client);                             // read/write the client's streams
}
  • new ServerSocket(port) associa e avvia l'ascolto. Usa 0 per lasciare che il sistema operativo scelga una porta libera (poi leggila con getLocalPort()).
  • accept() blocca finché un client non si connette, poi restituisce un Socket che rappresenta quella connessione. Il socket del server continua ad ascoltare il prossimo.

La connessione restituita da accept() è un normale Socket — identico a quello creato da un client — quindi la lettura e la scrittura dei suoi stream funzionano esattamente allo stesso modo.

Per default accept() blocca all'infinito. Chiama server.setSoTimeout(ms) prima se vuoi che lanci SocketTimeoutException dopo un'attesa, il che consente a un thread del server di verificare un flag "devo continuare?" invece di bloccarsi su una porta silenziosa.

Gestione concorrente dei client

accept() è bloccante e ogni client può mantenere la connessione aperta, quindi un ciclo a thread singolo può servire un solo client alla volta. La soluzione classica è un thread (o task in pool) per connessione:

ExecutorService pool = Executors.newFixedThreadPool(8);
while (running) {
    Socket client = server.accept();
    pool.submit(() -> handle(client));   // serve this client on a worker thread
}

Il ciclo di accept rimane libero di ricevere la connessione successiva mentre i worker servono quelle esistenti. Vedi il framework Executor e i thread pool per come dimensionare e gestire il pool. Con i virtual thread (Java 21+), "un thread per connessione" rimane economico anche con decine di migliaia di client, quindi spesso puoi evitare il pool e inviare ogni connessione al proprio virtual thread.

Il backlog

new ServerSocket(port, backlog) imposta il backlog — quante connessioni il sistema operativo può mettere in coda mentre il tuo codice è impegnato tra una chiamata accept() e l'altra. Oltre questo limite, le nuove connessioni vengono rifiutate. Il valore predefinito è tipicamente 50.

Un costruttore a quattro argomenti aggiunge l'indirizzo di bind: new ServerSocket(port, backlog, address). Passando InetAddress.getLoopbackAddress() il server è raggiungibile solo dalla stessa macchina (127.0.0.1) — utile per servizi solo locali e per l'esempio seguente.

Rilascio della porta: SO_REUSEADDR

Quando un server si ferma, la sua porta di ascolto può rimanere nel sistema operativo nello stato TIME_WAIT, e un nuovo avvio può fallire con "Address already in use". Impostare SO_REUSEADDR consente a un nuovo ServerSocket di associarsi a una porta ancora in TIME_WAIT:

ServerSocket server = new ServerSocket();      // unbound
server.setReuseAddress(true);
server.bind(new InetSocketAddress(8080));      // now bind explicitly

Ecco perché i server in produzione di solito creano un ServerSocket non associato, impostano le opzioni e poi chiamano bind() — invece di passare la porta al costruttore.

Esempio pratico: un server loopback concorrente

Questo programma associa un ServerSocket all'interfaccia di loopback, accetta tre client su un thread in background e serve ciascuno su un thread pool — poi lancia tre client verso di esso. Ogni worker saluta il proprio client e nomina il thread che lo ha servito, rendendo visibile la concorrenza.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • Il compito principale del server è bind → accept() → serve, ripetuto. accept() ha bloccato finché ogni client si è connesso, poi ha restituito un normale Socket per quel singolo client, mentre il ServerSocket è rimasto aperto per accettare il successivo — un solo listener, molte connessioni.
  • Associarsi alla porta 0 ha lasciato al sistema operativo la scelta di una porta libera, letta tramite getLocalPort() e passata ai client. Il terzo argomento del costruttore ha anche vincolato il server a getLoopbackAddress(), quindi ascoltava solo su 127.0.0.1 — la stessa coppia indirizzo-porta che i client hanno chiamato.
  • Ogni connessione accettata è stata passata a un thread pool, così il ciclo di accept non si è mai bloccato nel servire un client lento. Le risposte hanno nominato diversi thread worker (pool-1-thread-1, -2, -3), rendendo concreta la concorrenza per connessione: tre client sono stati serviti in parallelo, non uno dopo l'altro.
  • handle() ha usato try-with-resources sul socket client (try (client; …)), garantendo che ogni connessione venga chiusa dopo lo scambio. Un server che dimentica di chiudere i socket accettati perde descrittori rapidamente, poiché ne apre uno per ogni client.
  • L'arresto è stato esplicito e ordinato: stop dell'accept, pool.shutdown(), attesa del termine, poi server.close(). Un server a lungo termine deve rilasciare la propria porta di ascolto deliberatamente, e i task worker in sospeso devono poter terminare — la stessa disciplina si scala da questa demo a tre client a un server reale.

Quando usare ServerSocket

ServerSocket è lo strumento giusto quando hai bisogno di un server orientato alla connessione (TCP) che controlli a livello di byte/stream: un protocollo personalizzato, un backend per chat o giochi, un proxy o un esercizio didattico. Per request/response su HTTP normalmente useresti un framework server di livello superiore invece di scrivere il ciclo di accept a mano. Se hai bisogno di messaggistica senza connessione (UDP) — dove non c'è accept() e ogni pacchetto è indipendente — usa i datagram socket. Per informazioni di base su indirizzi, porte e stack protocollare, vedi l'introduzione al networking.

Pratica

Pratica
Un server usa un ciclo a thread singolo: 'while (true) { Socket c = server.accept(); handle(c); }' dove 'handle' mantiene la connessione aperta per tutta la sessione del client. Sotto carico, i nuovi client si connettono ma non ricevono risposta finché i precedenti non si disconnettono. Qual è la causa e la soluzione standard?
Un server usa un ciclo a thread singolo: 'while (true) { Socket c = server.accept(); handle(c); }' dove 'handle' mantiene la connessione aperta per tutta la sessione del client. Sotto carico, i nuovi client si connettono ma non ricevono risposta finché i precedenti non si disconnettono. Qual è la causa e la soluzione standard?
Was this page helpful?