W3docs

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. Use 0 para deixar o SO escolher uma porta livre (então leia de volta com getLocalPort()).
  • accept() bloqueia até que um cliente se conecte e então retorna um Socket representando 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.

java— editable, runs on the server

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 um Socket comum para aquele cliente, enquanto o ServerSocket permaneceu aberto para aceitar o próximo — um listener, muitas conexões.
  • Vincular à porta 0 deixou o SO escolher uma porta livre, lida de volta via getLocalPort() e passada aos clientes. O terceiro argumento do construtor também fixou o servidor em getLoopbackAddress(), de modo que ele escutou apenas em 127.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ão server.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.

Prática

Prática
Um servidor usa um loop de thread única: 'while (true) { Socket c = server.accept(); handle(c); }' onde 'handle' mantém a conexão aberta durante toda a sessão do cliente. Sob carga, novos clientes conectam mas não recebem resposta até que os anteriores desconectem. Qual é a causa e a solução padrão?
Um servidor usa um loop de thread única: 'while (true) { Socket c = server.accept(); handle(c); }' onde 'handle' mantém a conexão aberta durante toda a sessão do cliente. Sob carga, novos clientes conectam mas não recebem resposta até que os anteriores desconectem. Qual é a causa e a solução padrão?
Was this page helpful?