O módulo socket fornece a interface de baixo nível para comunicação de rede no Python. Ele permite criar clientes e servidores TCP, trocar datagramas UDP, trabalhar com IPv4, IPv6 e sockets locais, configurar timeouts, resolver nomes e acessar opções do sistema operacional. Bibliotecas como HTTP, SMTP e bancos de dados são construídas sobre conceitos semelhantes.
Sockets transportam bytes, não mensagens Python. O programa precisa definir framing, encoding, limites, timeouts, autenticação e tratamento de desconexão. Para protocolos conhecidos, prefira uma biblioteca de alto nível; use socket quando estiver implementando ou estudando um protocolo, integrando um serviço específico ou precisando de controle detalhado.
Endereços e famílias
AF_INET representa IPv4, AF_INET6 representa IPv6 e AF_UNIX, quando disponível, representa comunicação local por pathname. O tipo mais comum para TCP é SOCK_STREAM; para UDP, SOCK_DGRAM.
import socket
cliente = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
cliente.settimeout(5)
cliente.connect(("example.com", 80))
finally:
cliente.close()
Use context manager para fechar o descritor automaticamente.
Conecte com create_connection
socket.create_connection() resolve o host e tenta endereços compatíveis, simplificando clientes TCP.
import socket
with socket.create_connection(("example.com", 80), timeout=5) as sock:
sock.sendall(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
resposta = sock.recv(4096)
print(resposta[:100])
Para HTTP real, use uma biblioteca HTTP. O exemplo mostra apenas a camada de transporte e não implementa todos os detalhes do protocolo.
TCP é um fluxo
TCP entrega um fluxo ordenado de bytes. Uma chamada a send() no emissor não corresponde necessariamente a uma chamada a recv() no receptor. Dados podem chegar divididos ou agrupados.
O protocolo da aplicação precisa informar onde cada mensagem termina, por exemplo com tamanho prefixado, delimitador validado ou formato autodescritivo.
send e sendall
send() pode transmitir apenas parte do buffer e retorna a quantidade enviada. sendall() continua até enviar tudo ou ocorrer erro.
mensagem = b"dados importantes"
sock.sendall(mensagem)
Mesmo com sendall(), o receptor precisa montar o conteúdo completo. Um sucesso significa que os bytes foram entregues ao sistema local, não que a aplicação remota os processou.
recv e desconexão
recv(tamanho) retorna até o número solicitado. Um resultado vazio indica que o outro lado fechou a direção de envio de forma ordenada.
partes = []
while True:
bloco = sock.recv(4096)
if not bloco:
break
partes.append(bloco)
dados = b"".join(partes)
Nunca espere um recv() vazio se o protocolo mantém a conexão aberta para várias mensagens; use framing.
Framing com tamanho prefixado
Um padrão comum envia primeiro um inteiro de tamanho fixo e depois o payload.
import struct
def receber_exato(sock, quantidade):
partes = []
restante = quantidade
while restante:
bloco = sock.recv(restante)
if not bloco:
raise ConnectionError("conexão encerrada")
partes.append(bloco)
restante -= len(bloco)
return b"".join(partes)
cabecalho = receber_exato(sock, 4)
tamanho = struct.unpack("!I", cabecalho)[0]
if tamanho > 1_000_000:
raise ValueError("mensagem grande demais")
payload = receber_exato(sock, tamanho)
Valide o tamanho antes de alocar memória para evitar exaustão.
Servidor TCP básico
Um servidor cria o socket, associa um endereço, escuta e aceita conexões.
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as servidor:
servidor.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
servidor.bind(("127.0.0.1", 9000))
servidor.listen()
conexao, endereco = servidor.accept()
with conexao:
conexao.sendall(b"ola\n")
Associar a 127.0.0.1 limita acesso à máquina local. Usar endereço vazio ou todas as interfaces expõe o serviço à rede e exige autenticação, firewall e hardening.
listen e backlog
listen() coloca o socket em modo passivo. O backlog controla uma fila de conexões pendentes, mas o comportamento exato depende do sistema.
Um backlog grande não resolve um servidor lento. Aceite conexões rapidamente, limite clientes e aplique timeouts.
accept cria outro socket
accept() devolve um novo socket conectado e o endereço remoto. O socket de escuta continua disponível para novos clientes.
Feche ambos de forma independente. Um vazamento de conexões aceitas esgota descritores.
Concorrência no servidor
Um servidor simples trata um cliente por vez. Para vários clientes, use threads, processos, selectors ou asyncio. A escolha depende de quantidade de conexões, workload e complexidade.
O artigo de select no Python mostra multiplexação de descritores. Não crie uma thread ilimitada por conexão sem controles.
Timeouts
settimeout(segundos) limita operações bloqueantes.
sock.settimeout(10)
try:
dados = sock.recv(4096)
except socket.timeout:
registrar_aviso("cliente inativo")
Defina timeouts para conexão, leitura e escrita de acordo com o protocolo. Uma conexão sem deadline pode prender workers indefinidamente.
Modo não bloqueante
setblocking(False) faz operações retornarem imediatamente e gerarem BlockingIOError quando ainda não podem avançar.
Não implemente busy loop. Use selectors, select ou um event loop para aguardar readiness.
UDP
UDP envia datagramas independentes sem conexão confiável, ordenação ou retransmissão automática.
import socket
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
sock.settimeout(2)
sock.sendto(b"consulta", ("127.0.0.1", 9999))
dados, origem = sock.recvfrom(2048)
print(origem, dados)
Datagramas podem ser perdidos, duplicados, reordenados ou truncados se o buffer for pequeno. O protocolo da aplicação decide retries e idempotência.
Tamanho de UDP
Embora o protocolo permita datagramas grandes, redes reais têm limites de MTU e fragmentação. Mensagens pequenas são mais robustas. Para payloads extensos ou confiabilidade, use TCP ou um protocolo apropriado.
Resolução de nomes
getaddrinfo() resolve nomes para endereços e retorna alternativas de família e tipo.
for resultado in socket.getaddrinfo("example.com", 443, type=socket.SOCK_STREAM):
familia, tipo, protocolo, canonico, endereco = resultado
print(familia, endereco)
DNS pode ser lento, falhar ou retornar vários endereços. Não fixe a primeira resposta para sempre sem política de cache.
IPv4 e IPv6
Use getaddrinfo() e tente os endereços retornados em vez de assumir IPv4. IPv6 usa tuplas de endereço diferentes e pode exigir configuração específica de dual stack.
Teste em ambientes IPv4-only, IPv6-only e dual stack.
Portas e ordem de bytes
Portas variam de 0 a 65535. Portas baixas podem exigir privilégios. Funções como htons(), htonl(), ntohs() e ntohl() convertem ordem de bytes para campos binários, embora struct com prefixo ! seja comum em protocolos.
SO_REUSEADDR
SO_REUSEADDR pode facilitar reinício de servidores após conexões em estados de encerramento. A semântica varia por sistema e não deve ser interpretada como autorização para vários servidores concorrentes na mesma porta.
Configure opções antes de bind().
Keepalive
SO_KEEPALIVE habilita probes do sistema para detectar conexões quebradas, mas intervalos padrão podem ser longos e opções específicas variam.
Keepalive não substitui heartbeat e deadline no protocolo da aplicação.
shutdown
shutdown(socket.SHUT_WR) informa que o programa não enviará mais dados, mantendo a possibilidade de receber uma resposta. close() libera o descritor local.
Use half-close somente quando o protocolo o compreender. Um peer pode tratar como encerramento total.
TLS
Um socket TCP comum não oferece confidencialidade, autenticação ou integridade contra intermediários. Para protocolos seguros, envolva a conexão com ssl.SSLContext configurado corretamente ou use uma biblioteca de alto nível.
Não crie criptografia própria e não desative verificação de certificado para “resolver” erros.
Encoding
A rede transporta bytes. Defina explicitamente o encoding para texto.
mensagem = "olá".encode("utf-8")
sock.sendall(mensagem)
texto = dados.decode("utf-8", errors="strict")
Delimitadores devem ser definidos em bytes e o protocolo precisa tratar caracteres inválidos.
Segurança de entrada
Limite tamanho, profundidade, quantidade de campos e tempo de processamento. Não passe dados de rede diretamente a eval(), shell, SQL ou deserializadores inseguros.
Autentique clientes quando necessário e aplique autorização por operação.
Slow clients
Um cliente pode enviar poucos bytes lentamente e ocupar um worker. Use deadlines, tamanho mínimo por progresso, limites de conexão e multiplexação.
Rate limiting e backpressure devem proteger memória e serviços downstream.
Erros transitórios
Conexões podem falhar por DNS, timeout, reset, rota, recusa e indisponibilidade temporária. Refaça apenas operações idempotentes e use atraso exponencial com limite.
Não repita indefinidamente nem transforme falha de autenticação em retry.
Endereços locais Unix
Quando AF_UNIX estiver disponível, processos da mesma máquina podem comunicar-se por pathname com menos exposição à rede.
Gerencie permissões do arquivo do socket, remova paths órfãos e limite acesso ao diretório.
Testes
Teste pacotes divididos, vários frames juntos, EOF, timeout, reset, endereço inválido, IPv6, DNS com várias respostas, cliente lento, payload máximo, conexão simultânea, shutdown e indisponibilidade de rede.
Use sockets locais e portas efêmeras em testes. Não dependa de serviços públicos.
Observabilidade
Registre endereço, duração, bytes, estado do protocolo e erro sem armazenar segredos ou payloads sensíveis. Métricas de conexões ativas, timeouts, resets e filas ajudam a detectar saturação.
Erros comuns
Os erros mais frequentes são presumir que recv() retorna uma mensagem completa, ignorar retorno de send(), não usar timeout, expor o servidor em todas as interfaces, confiar em UDP, bloquear uma thread por cliente sem limite, usar DNS como identidade e transportar dados sensíveis sem TLS.
Conclusão
socket oferece controle direto sobre a rede, mas exige um protocolo explícito. Defina framing, limites, timeouts, segurança, lifecycle e estratégia de concorrência antes de colocar o serviço em produção.
Consulte a documentação oficial de socket e select no Python para multiplexar várias conexões.







