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. Usa0per lasciare che il sistema operativo scelga una porta libera (poi leggila congetLocalPort()).accept()blocca finché un client non si connette, poi restituisce unSocketche 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 explicitlyEcco 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.
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 normaleSocketper quel singolo client, mentre ilServerSocketè rimasto aperto per accettare il successivo — un solo listener, molte connessioni. - Associarsi alla porta
0ha lasciato al sistema operativo la scelta di una porta libera, letta tramitegetLocalPort()e passata ai client. Il terzo argomento del costruttore ha anche vincolato il server agetLoopbackAddress(), quindi ascoltava solo su127.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, poiserver.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.