socketserver no Python: crie servidores

Publicado em: 19/08/2026
Tempo de leitura: 6 minutos
Cabos Ethernet conectados representando servidores de rede com socketserver no Python

O módulo socketserver simplifica a criação de servidores de rede no Python. Em vez de repetir manualmente o ciclo de criar um socket, fazer bind, escutar conexões, aceitar clientes e instanciar handlers, você escolhe uma classe de servidor e implementa apenas o método que processa cada requisição.

Essa abstração é útil em serviços internos, ferramentas de teste, protocolos simples, agentes locais e protótipos. Ainda assim, um servidor de rede precisa de limites, timeouts, framing, concorrência e encerramento previsível. Este guia mostra como usar TCPServer, UDPServer, StreamRequestHandler, servidores com threads e os principais cuidados de produção.

Como o socketserver é organizado

O módulo fornece quatro classes concretas básicas: TCPServer, UDPServer, UnixStreamServer e UnixDatagramServer. As duas últimas usam sockets Unix e não estão disponíveis em todas as plataformas. Por padrão, os servidores processam uma requisição de cada vez.

Para atender clientes simultaneamente, existem os mixins ThreadingMixIn e ForkingMixIn, além de classes prontas como ThreadingTCPServer e ThreadingUDPServer. O modelo com fork depende de POSIX e cria um novo processo, enquanto o modelo com threads compartilha memória e exige sincronização para estado mutável.

Antes de avançar, vale revisar o guia de cliente TCP com sockets no Python. socketserver não altera as propriedades de TCP ou UDP; apenas organiza o código do servidor.

Primeiro servidor TCP com StreamRequestHandler

StreamRequestHandler cria os objetos rfile e wfile, permitindo ler e escrever como em arquivos binários. O exemplo abaixo usa uma linha terminada por \n como framing.

import socketserver

MAX_LINE = 10_000

class EchoHandler(socketserver.StreamRequestHandler):
    def handle(self) -> None:
        line = self.rfile.readline(MAX_LINE + 1)

        if len(line) > MAX_LINE:
            self.wfile.write(b"erro: mensagem muito grande\n")
            return

        if not line.endswith(b"\n"):
            self.wfile.write(b"erro: mensagem incompleta\n")
            return

        response = line.rstrip(b"\r\n").upper() + b"\n"
        self.wfile.write(response)

HOST, PORT = "127.0.0.1", 9000

with socketserver.TCPServer((HOST, PORT), EchoHandler) as server:
    server.serve_forever()

O limite de leitura é indispensável. Sem ele, um cliente pode enviar dados indefinidamente sem completar a linha, fazendo o processo acumular memória. Também é importante definir a codificação quando o protocolo usa texto, em vez de chamar decode() sem tratamento de erro.

TCP é um fluxo, não um conjunto de mensagens

Uma chamada sendall() do cliente não corresponde necessariamente a uma chamada recv() no servidor. Os bytes podem chegar divididos ou agrupados. Por isso, o protocolo precisa definir framing: linha terminada por delimitador, tamanho prefixado, formato binário de tamanho fixo ou fechamento da conexão.

O artigo sobre struct e dados binários no Python mostra como criar cabeçalhos com tamanho explícito. Para protocolos textuais simples, uma linha limitada costuma ser suficiente.

Servidor concorrente com threads

TCPServer é síncrono. Enquanto um cliente está sendo atendido, os demais aguardam. Para conexões com I/O ou clientes lentos, use ThreadingTCPServer.

class SafeThreadingTCPServer(socketserver.ThreadingTCPServer):
    allow_reuse_address = True
    daemon_threads = False
    block_on_close = True

with SafeThreadingTCPServer((HOST, PORT), EchoHandler) as server:
    server.serve_forever()

daemon_threads=False faz o Python aguardar as threads ativas antes de sair. block_on_close=True mantém o encerramento previsível. Se você escolher threads daemon, documente que requisições podem ser interrompidas durante o desligamento.

O guia de threading no Python explica locks, condições de corrida e quando threads ajudam. O GIL não elimina race conditions em estruturas compartilhadas.

Estado compartilhado e locks

Cada conexão recebe uma nova instância do handler, mas todas podem acessar self.server. Ao compartilhar contadores ou caches, proteja atualizações compostas.

import threading

class CountingServer(socketserver.ThreadingTCPServer):
    daemon_threads = False

    def __init__(self, address, handler):
        super().__init__(address, handler)
        self.total_requests = 0
        self.counter_lock = threading.Lock()

class CountingHandler(socketserver.StreamRequestHandler):
    def handle(self) -> None:
        with self.server.counter_lock:
            self.server.total_requests += 1
            current = self.server.total_requests

        self.wfile.write(f"requisição {current}\n".encode("utf-8"))

Evite manter sessões importantes apenas em memória quando usar processos, múltiplas instâncias ou reinicializações. Prefira armazenamento externo apropriado quando o estado precisa sobreviver ou ser compartilhado.

Timeout por conexão

Clientes lentos podem manter threads ocupadas. Configure timeout no socket aceito no método setup().

class TimedHandler(socketserver.StreamRequestHandler):
    timeout_seconds = 10

    def setup(self) -> None:
        super().setup()
        self.request.settimeout(self.timeout_seconds)

    def handle(self) -> None:
        try:
            line = self.rfile.readline(4097)
        except TimeoutError:
            return

        if not line or len(line) > 4096:
            return

        self.wfile.write(b"ok\n")

O atributo server.timeout afeta handle_request(), não o loop de serve_forever() nem automaticamente cada cliente. Para timeouts de sessão, configure o socket da conexão.

Backpressure e fila de conexões

request_queue_size controla aproximadamente quantas conexões aguardam aceitação quando o servidor está ocupado. Aumentar o valor não substitui limites de concorrência; apenas desloca a espera.

class LimitedServer(socketserver.ThreadingTCPServer):
    request_queue_size = 32
    allow_reuse_address = True

ThreadingTCPServer cria uma thread por conexão e não impõe um pool fixo. Um atacante pode abrir muitas conexões e consumir threads. Em serviço exposto, combine firewall, proxy, limites do sistema operacional, timeouts e, quando necessário, uma arquitetura com pool ou event loop.

Filtrar clientes com verify_request()

O método verify_request() pode rejeitar uma requisição antes do handler.

import ipaddress

ALLOWED = ipaddress.ip_network("10.10.0.0/16")

class InternalServer(socketserver.ThreadingTCPServer):
    def verify_request(self, request, client_address) -> bool:
        client_ip = ipaddress.ip_address(client_address[0])
        return client_ip in ALLOWED

Uma allowlist por IP é apenas uma camada. Endereços podem mudar, proxies alteram a origem observada e redes internas também podem ser comprometidas. Para autenticação forte, use TLS com certificados, HMAC ou credenciais do protocolo. O guia de ssl e TLS no Python cobre a proteção do canal.

Servidor UDP

UDP trabalha com datagramas independentes, que podem ser perdidos, duplicados ou chegar fora de ordem.

class UDPHandler(socketserver.BaseRequestHandler):
    def handle(self) -> None:
        data, udp_socket = self.request

        if len(data) > 1024:
            return

        response = data.strip().upper()
        udp_socket.sendto(response, self.client_address)

with socketserver.ThreadingUDPServer((HOST, 9001), UDPHandler) as server:
    server.serve_forever()

Não confie no endereço de origem UDP como autenticação. Evite respostas muito maiores que as solicitações para não criar um vetor de amplificação. Protocolos importantes precisam de IDs, proteção contra replay e tratamento de perda.

Tratamento de erros

Por padrão, uma exceção no handler é impressa em stderr e o servidor continua. Sobrescreva handle_error() para integrar logs estruturados sem expor payloads.

import logging

logger = logging.getLogger(__name__)

class LoggedServer(socketserver.ThreadingTCPServer):
    def handle_error(self, request, client_address) -> None:
        logger.exception(
            "falha ao processar cliente",
            extra={"client_ip": client_address[0]},
        )

O guia de logging no Python ajuda a organizar níveis, handlers e dados de contexto. Não registre segredos nem mensagens completas por padrão.

Encerramento correto

serve_forever() para somente após uma chamada a shutdown(). Essa chamada precisa vir de outra thread; chamá-la dentro da mesma thread do loop causa deadlock.

import threading

server = SafeThreadingTCPServer((HOST, PORT), EchoHandler)
thread = threading.Thread(target=server.serve_forever)
thread.start()

try:
    thread.join()
except KeyboardInterrupt:
    server.shutdown()
    server.server_close()
    thread.join()

Em uma aplicação real, trate sinais no processo principal, pare de aceitar novas conexões, aguarde o trabalho em andamento até um prazo e então libere recursos.

IPv6

Para escutar em IPv6, ajuste address_family.

import socket

class IPv6TCPServer(socketserver.ThreadingTCPServer):
    address_family = socket.AF_INET6

Teste dual stack em cada sistema, pois o comportamento de IPv4 mapeado pode variar. Valide endereços com o módulo ipaddress quando aplicar regras.

Quando usar socketserver?

Use o módulo para protocolos pequenos, ferramentas internas, mocks, servidores locais e aprendizado. Para HTTP público, prefira um framework e servidor WSGI/ASGI. Para milhares de conexões duradouras, considere asyncio ou uma arquitetura orientada a eventos. Para lógica pesada de CPU, filas externas e processos controlados oferecem limites melhores que uma thread por conexão.

Erros comuns

Os problemas mais frequentes são ler sem limite, assumir que um recv() contém uma mensagem inteira, usar o servidor síncrono com clientes lentos, compartilhar estado sem lock, criar threads ilimitadas, omitir timeout, confiar apenas no IP, chamar shutdown() na thread errada e expor um servidor de demonstração à internet.

Boas práticas

Defina framing, tamanho máximo e timeout. Escolha conscientemente entre síncrono, threads, processos ou event loop. Proteja estado compartilhado. Use TLS e autenticação quando necessário. Implemente logs sem payload sensível, métricas de conexões e encerramento testável. Coloque serviços públicos atrás de controles de rede e limites adicionais.

Conclusão

socketserver reduz bastante o código necessário para criar servidores TCP e UDP, mas não remove as responsabilidades de um protocolo de rede. Limites, framing, concorrência, autenticação e desligamento continuam essenciais. Para serviços pequenos e controlados, o módulo oferece uma estrutura clara; para cargas públicas e complexas, use abstrações com pools, event loops e infraestrutura de produção.

Consulte a documentação oficial de socketserver e o RFC 9293 sobre TCP.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Desenvolvedor configurando servidor HTTPS e certificado TLS com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crie servidor HTTPS local no Python

    Aprenda HTTPSServer no Python para servir HTTPS localmente, configurar certificados, usar threads e entender limites de segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    08/10/2026
    Arquivos protegidos representando extração segura de TAR com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extraia TAR com segurança

    Aprenda tarfile extraction_filter no Python para extrair arquivos TAR com validação, segurança e controle de caminhos.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python exibindo avisos controlados com catch_warnings
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: capture warnings em testes Python

    Aprenda catch_warnings no Python para capturar, testar e controlar avisos com filtros específicos e segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python representando referências persistentes do pickle
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serialize referências externas

    Aprenda pickle persistent_id no Python para serializar referências externas com IDs estáveis, validação, segurança e compatibilidade.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binário representando buffers e memória no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: conte valores sem copiar buffers

    Aprenda memoryview.count no Python para contar bytes e valores em buffers sem cópias, com formatos, limites e boas práticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python com anotações de tipos
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evite imports circulares em anotações

    Aprenda annotationlib no Python para ler anotações adiadas, evitar imports circulares e criar ferramentas de introspecção seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026