Java ServerSocket
Acepta conexiones TCP entrantes en Java con ServerSocket y crea un servidor simple.
El extremo cliente de una conexión TCP es Socket. El extremo del servidor es java.net.ServerSocket: se vincula a un puerto, escucha las conexiones entrantes y te entrega un Socket normal por cada cliente que llega. Un ServerSocket acepta muchos clientes; cada llamada a accept() devuelve una conexión separada.
Este capítulo cubre cómo vincular y aceptar conexiones, cómo atender a muchos clientes a la vez, el papel del backlog de conexiones y la disciplina del ciclo de vida que necesita un servidor de larga duración.
Vincular, aceptar y servir
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)vincula e inicia la escucha. Usa0para dejar que el sistema operativo elija un puerto libre (luego léelo congetLocalPort()).accept()se bloquea hasta que un cliente se conecta y devuelve unSocketque representa esa conexión. El socket de servidor continúa escuchando la siguiente.
La conexión que devuelve accept() es un Socket ordinario — idéntico al que crea un cliente — por lo que leer y escribir en sus flujos funciona exactamente igual.
Por defecto, accept() se bloquea indefinidamente. Llama primero a server.setSoTimeout(ms) si quieres que lance SocketTimeoutException tras una espera, lo que permite a un hilo del servidor verificar un indicador "¿debo seguir ejecutándome?" en lugar de quedarse colgado en un puerto silencioso.
Manejo concurrente de clientes
accept() es bloqueante y cada cliente puede mantener su conexión abierta, por lo que un bucle de un solo hilo solo puede atender a un cliente a la vez. La solución clásica es un hilo (o tarea en pool) por conexión:
ExecutorService pool = Executors.newFixedThreadPool(8);
while (running) {
Socket client = server.accept();
pool.submit(() -> handle(client)); // serve this client on a worker thread
}El bucle de aceptación permanece libre para tomar la siguiente conexión mientras los trabajadores atienden a los existentes. Consulta el framework Executor y los pools de hilos para saber cómo dimensionar y gestionar el pool. Con los hilos virtuales (Java 21+), "un hilo por conexión" sigue siendo económico incluso con decenas de miles de clientes, por lo que a menudo puedes omitir el pool y simplemente enviar cada conexión a su propio hilo virtual.
El backlog
new ServerSocket(port, backlog) establece el backlog — cuántas conexiones puede encolar el sistema operativo mientras tu código está ocupado entre llamadas a accept(). Más allá de ese límite, las nuevas conexiones son rechazadas. El valor predeterminado suele ser 50.
Un constructor de cuatro argumentos agrega la dirección de vinculación: new ServerSocket(port, backlog, address). Pasar InetAddress.getLoopbackAddress() hace que el servidor solo sea accesible desde la misma máquina (127.0.0.1) — útil para servicios de solo uso local y para el ejemplo a continuación.
Liberar el puerto: SO_REUSEADDR
Cuando un servidor se detiene, su puerto de escucha puede permanecer en el sistema operativo en estado TIME_WAIT, y un nuevo inicio puede fallar con "Address already in use". Establecer SO_REUSEADDR permite que un nuevo ServerSocket se vincule a un puerto que aún esté en TIME_WAIT:
ServerSocket server = new ServerSocket(); // unbound
server.setReuseAddress(true);
server.bind(new InetSocketAddress(8080)); // now bind explicitlyPor eso, los servidores de producción generalmente crean un ServerSocket no vinculado, establecen opciones y luego llaman a bind() — en lugar de pasar el puerto al constructor.
Un ejemplo completo: un servidor loopback concurrente
Este programa vincula un ServerSocket a la interfaz loopback, acepta tres clientes en un hilo en segundo plano y atiende a cada uno en un pool de hilos — luego lanza tres clientes contra él. Cada trabajador saluda a su cliente y nombra el hilo que lo atendió, por lo que la concurrencia es visible.
Lo que hay que extraer de la ejecución:
- El trabajo completo del servidor es vincular →
accept()→ servir, repetido.accept()se bloqueó hasta que cada cliente se conectó y luego devolvió unSocketordinario para ese cliente en particular, mientras que elServerSocketpermaneció abierto para aceptar el siguiente — un oyente, muchas conexiones. - Vincular al puerto
0permitió que el sistema operativo eligiera un puerto libre, leído de vuelta mediantegetLocalPort()y pasado a los clientes. El tercer argumento del constructor también fijó el servidor agetLoopbackAddress(), por lo que escuchó solo en127.0.0.1— el mismo par dirección-puerto al que llamaron los clientes. - Cada conexión aceptada fue entregada a un pool de hilos, por lo que el bucle de aceptación nunca se bloqueó al atender a un cliente lento. Las respuestas nombraron diferentes hilos trabajadores (
pool-1-thread-1,-2,-3), haciendo concreta la concurrencia por conexión: tres clientes fueron atendidos en paralelo, no uno tras otro. handle()usó try-with-resources en el socket del cliente (try (client; …)), garantizando que cada conexión se cierre después de su intercambio. Un servidor que olvida cerrar los sockets aceptados pierde descriptores rápidamente, ya que abre uno por cliente.- El cierre fue explícito y ordenado: dejar de aceptar,
pool.shutdown(), esperar la terminación y luegoserver.close(). Un servidor de larga duración debe liberar su puerto de escucha deliberadamente, y las tareas de trabajadores pendientes deben poder finalizar — la misma disciplina escala desde esta demo de tres clientes hasta un servidor real.
Cuándo usar ServerSocket
ServerSocket es la herramienta adecuada cuando necesitas un servidor orientado a conexión (TCP) que controlas a nivel de byte/flujo: un protocolo personalizado, un backend de chat o juego, un proxy o un ejercicio de aprendizaje. Para solicitud/respuesta sobre HTTP normalmente usarías un framework de servidor de nivel superior en lugar de escribir el bucle de aceptación a mano. Si necesitas mensajería sin conexión (UDP) — donde no hay accept() y cada paquete es independiente — usa datagram sockets. Para información de fondo sobre direcciones, puertos y la pila de protocolos, consulta la introducción a redes.