Java ServerSocket
Aceite conexões TCP em Java com ServerSocket e crie um servidor simples.
O lado cliente de uma conexão TCP é Socket. O lado servidor é java.net.ServerSocket: ele vincula a uma porta, escuta conexões de entrada e entrega um Socket comum para cada cliente que chega. Um ServerSocket aceita muitos clientes; cada chamada accept() retorna uma conexão separada.
Este capítulo explica como vincular e aceitar conexões, como atender múltiplos clientes ao mesmo tempo, o papel do backlog de conexões e a disciplina de ciclo de vida que um servidor de longa duração precisa.
Vincular, aceitar e atender
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 começa a escutar. Use0para deixar o SO escolher uma porta livre (então leia de volta comgetLocalPort()).accept()bloqueia até que um cliente se conecte e então retorna umSocketrepresentando aquela conexão. O socket do servidor continua escutando o próximo.
A conexão que accept() retorna é um Socket comum — idêntico ao que um cliente cria — portanto ler e escrever nos seus streams funciona exatamente da mesma forma.
Por padrão, accept() bloqueia indefinidamente. Chame server.setSoTimeout(ms) antes se quiser que ele lance SocketTimeoutException após uma espera, o que permite que uma thread de servidor verifique uma flag "devo continuar rodando?" em vez de ficar parada em uma porta silenciosa.
Tratando clientes de forma concorrente
accept() é bloqueante e cada cliente pode manter sua conexão aberta, então um loop de thread única só pode atender um cliente por vez. A solução clássica é uma thread (ou tarefa em pool) por conexão:
ExecutorService pool = Executors.newFixedThreadPool(8);
while (running) {
Socket client = server.accept();
pool.submit(() -> handle(client)); // serve this client on a worker thread
}O loop de aceitação fica livre para receber a próxima conexão enquanto os workers atendem as existentes. Veja o framework Executor e thread pools para saber como dimensionar e gerenciar o pool. Com threads virtuais (Java 21+), "uma thread por conexão" continua barato mesmo com dezenas de milhares de clientes, então frequentemente é possível pular o pool e simplesmente submeter cada conexão à sua própria thread virtual.
O backlog
new ServerSocket(port, backlog) define o backlog — quantas conexões o SO pode enfileirar enquanto seu código está ocupado entre chamadas accept(). Além desse limite, novas conexões são recusadas. O padrão é tipicamente 50.
Um construtor de quatro argumentos adiciona o endereço de vinculação: new ServerSocket(port, backlog, address). Passar InetAddress.getLoopbackAddress() faz o servidor ser acessível apenas da mesma máquina (127.0.0.1) — útil para serviços locais e para o exemplo abaixo.
Liberando a porta: SO_REUSEADDR
Quando um servidor para, sua porta de escuta pode permanecer no SO em estado TIME_WAIT, e uma nova inicialização pode falhar com "Address already in use". Definir SO_REUSEADDR permite que um novo ServerSocket vincule a uma porta ainda em TIME_WAIT:
ServerSocket server = new ServerSocket(); // unbound
server.setReuseAddress(true);
server.bind(new InetSocketAddress(8080)); // now bind explicitlyÉ por isso que servidores em produção geralmente criam um ServerSocket não vinculado, definem opções e depois chamam bind() — em vez de passar a porta ao construtor.
Um exemplo prático: um servidor loopback concorrente
Este programa vincula um ServerSocket à interface loopback, aceita três clientes em uma thread de segundo plano e atende cada um em um thread pool — depois dispara três clientes contra ele. Cada worker cumprimenta seu cliente e nomeia a thread que o atendeu, tornando a concorrência visível.
O que observar na execução:
- O trabalho completo do servidor é vincular →
accept()→ atender, repetidamente.accept()bloqueou até cada cliente conectar e então retornou umSocketcomum para aquele cliente, enquanto oServerSocketpermaneceu aberto para aceitar o próximo — um listener, muitas conexões. - Vincular à porta
0deixou o SO escolher uma porta livre, lida de volta viagetLocalPort()e passada aos clientes. O terceiro argumento do construtor também fixou o servidor emgetLoopbackAddress(), de modo que ele escutou apenas em127.0.0.1— o mesmo par endereço-e-porta que os clientes discaram. - Cada conexão aceita foi entregue a um thread pool, então o loop de aceitação nunca bloqueou ao atender um cliente lento. As respostas nomearam diferentes threads de worker (
pool-1-thread-1,-2,-3), tornando concreta a concorrência por conexão: três clientes foram atendidos em paralelo, não um após o outro. handle()usou try-with-resources no socket do cliente (try (client; …)), garantindo que cada conexão seja fechada após sua troca. Um servidor que esquece de fechar sockets aceitos vaza descritores rapidamente, pois abre um por cliente.- O encerramento foi explícito e ordenado: parar de aceitar,
pool.shutdown(), aguardar o término e entãoserver.close(). Um servidor de longa duração deve liberar sua porta de escuta deliberadamente, e as tarefas de worker pendentes devem ser concluídas — a mesma disciplina escala desta demonstração de três clientes para um servidor real.
Quando usar ServerSocket
ServerSocket é a ferramenta certa quando você precisa de um servidor orientado a conexão (TCP) que você controla no nível de byte/stream: um protocolo personalizado, um backend de chat ou jogo, um proxy ou um exercício de aprendizado. Para request/response sobre HTTP você normalmente usaria um framework de servidor de nível mais alto em vez de escrever o loop de aceitação manualmente. Se você precisa de mensagens sem conexão (UDP) — onde não há accept() e cada pacote é independente — use datagram sockets. Para informações sobre endereços, portas e a pilha de protocolos, veja a introdução a networking.