W3docs

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. Usa 0 para dejar que el sistema operativo elija un puerto libre (luego léelo con getLocalPort()).
  • accept() se bloquea hasta que un cliente se conecta y devuelve un Socket que 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 explicitly

Por 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.

java— editable, runs on the server

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ó un Socket ordinario para ese cliente en particular, mientras que el ServerSocket permaneció abierto para aceptar el siguiente — un oyente, muchas conexiones.
  • Vincular al puerto 0 permitió que el sistema operativo eligiera un puerto libre, leído de vuelta mediante getLocalPort() y pasado a los clientes. El tercer argumento del constructor también fijó el servidor a getLoopbackAddress(), por lo que escuchó solo en 127.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 luego server.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.

Práctica

Práctica
Un servidor usa un bucle de un solo hilo: 'while (true) { Socket c = server.accept(); handle(c); }' donde 'handle' mantiene la conexión abierta durante toda la sesión del cliente. Bajo carga, los nuevos clientes se conectan pero no reciben respuesta hasta que los anteriores se desconectan. ¿Cuál es la causa y la solución estándar?
Un servidor usa un bucle de un solo hilo: 'while (true) { Socket c = server.accept(); handle(c); }' donde 'handle' mantiene la conexión abierta durante toda la sesión del cliente. Bajo carga, los nuevos clientes se conectan pero no reciben respuesta hasta que los anteriores se desconectan. ¿Cuál es la causa y la solución estándar?
Was this page helpful?