Sockets: comunicação em rede com Python
1. Introdução aos Sockets em Python
Sockets são endpoints de comunicação bidirecional entre processos, seja na mesma máquina ou através de uma rede. Eles formam a base de praticamente toda comunicação na internet, permitindo que programas troquem dados de forma estruturada. Em Python, o módulo socket da biblioteca padrão fornece uma interface direta para as APIs de sockets do sistema operacional.
Existem dois tipos principais de sockets:
- TCP (SOCK_STREAM): Orientado a conexão, garante entrega ordenada e confiável dos dados. Ideal para aplicações como navegadores web e servidores de email.
- UDP (SOCK_DGRAM): Sem conexão, não garante entrega nem ordem. Mais rápido e leve, usado em streaming de vídeo e jogos online.
2. Configuração do Ambiente e Primeiro Socket
Para começar, importamos o módulo socket e criamos nosso primeiro socket:
import socket
# Criando um socket TCP (IPv4)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Criando um socket UDP (IPv4)
udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
Os parâmetros essenciais são:
- AF_INET: Família de endereços IPv4
- SOCK_STREAM: Socket TCP
- SOCK_DGRAM: Socket UDP
Sempre trate exceções e feche os sockets adequadamente:
import socket
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# ... operações com o socket
except socket.error as e:
print(f"Erro ao criar socket: {e}")
finally:
sock.close()
# Alternativa moderna com gerenciador de contexto
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# o socket é fechado automaticamente
pass
3. Comunicação TCP: Cliente e Servidor
Servidor TCP
O servidor precisa fazer bind (vincular a um endereço e porta), listen (aguardar conexões) e accept (aceitar uma conexão):
import socket
HOST = '127.0.0.1' # localhost
PORT = 65432 # porta não privilegiada
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
server_socket.bind((HOST, PORT))
server_socket.listen()
print(f"Servidor ouvindo em {HOST}:{PORT}")
conn, addr = server_socket.accept()
with conn:
print(f"Conectado por {addr}")
while True:
data = conn.recv(1024)
if not data:
break
conn.sendall(data) # echo
Cliente TCP
O cliente usa connect para estabelecer conexão e send/recv para trocar dados:
import socket
HOST = '127.0.0.1'
PORT = 65432
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
client_socket.connect((HOST, PORT))
client_socket.sendall(b'Olá, servidor!')
data = client_socket.recv(1024)
print(f"Recebido: {data.decode()}")
4. Comunicação UDP: Envio sem Conexão
Servidor UDP
No UDP, não há conexão estabelecida. O servidor apenas recebe datagramas:
import socket
HOST = '127.0.0.1'
PORT = 65432
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as server_socket:
server_socket.bind((HOST, PORT))
print(f"Servidor UDP ouvindo em {HOST}:{PORT}")
data, addr = server_socket.recvfrom(1024)
print(f"Recebido de {addr}: {data.decode()}")
server_socket.sendto(b'Resposta do servidor', addr)
Cliente UDP
O cliente envia dados sem estabelecer conexão prévia:
import socket
HOST = '127.0.0.1'
PORT = 65432
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as client_socket:
client_socket.sendto(b'Olá, UDP!', (HOST, PORT))
data, addr = client_socket.recvfrom(1024)
print(f"Recebido: {data.decode()}")
Diferenças práticas: TCP garante entrega e ordem, mas tem overhead maior. UDP é mais rápido, mas não confiável — pacotes podem ser perdidos ou chegar fora de ordem.
5. Gerenciamento de Múltiplas Conexões
Usando select para monitorar múltiplos sockets
O módulo select permite monitorar vários sockets simultaneamente:
import socket
import select
HOST = '127.0.0.1'
PORT = 65432
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((HOST, PORT))
server_socket.listen()
server_socket.setblocking(False)
sockets_list = [server_socket]
print(f"Servidor com select ouvindo em {HOST}:{PORT}")
while True:
read_sockets, _, _ = select.select(sockets_list, [], [])
for sock in read_sockets:
if sock == server_socket:
conn, addr = server_socket.accept()
conn.setblocking(False)
sockets_list.append(conn)
print(f"Nova conexão de {addr}")
else:
data = sock.recv(1024)
if data:
sock.sendall(data)
else:
sockets_list.remove(sock)
sock.close()
Usando socketserver para servidores concorrentes
O módulo socketserver simplifica a criação de servidores que lidam com múltiplas conexões:
import socketserver
class EchoHandler(socketserver.BaseRequestHandler):
def handle(self):
data = self.request.recv(1024)
print(f"Recebido de {self.client_address}: {data.decode()}")
self.request.sendall(data)
HOST = '127.0.0.1'
PORT = 65432
with socketserver.ThreadingTCPServer((HOST, PORT), EchoHandler) as server:
print(f"Servidor concorrente em {HOST}:{PORT}")
server.serve_forever()
6. Tratamento de Erros e Timeouts
Configurar timeouts evita que o programa trave indefinidamente:
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5.0) # timeout de 5 segundos
try:
sock.connect(('exemplo.com', 80))
sock.sendall(b'GET / HTTP/1.1\r\nHost: exemplo.com\r\n\r\n')
data = sock.recv(4096)
except socket.timeout:
print("Timeout: servidor não respondeu")
except ConnectionRefusedError:
print("Conexão recusada: servidor não está disponível")
except BrokenPipeError:
print("Conexão perdida durante comunicação")
except socket.error as e:
print(f"Erro de socket: {e}")
finally:
sock.close()
Para operações não bloqueantes:
sock.setblocking(False) # lançaria BlockingIOError se não houver dados
7. Envio e Recebimento de Dados Complexos
Serialização com pickle
Para enviar objetos Python complexos:
import socket
import pickle
# Cliente
data = {'nome': 'Alice', 'idade': 30, 'cidade': 'São Paulo'}
serialized = pickle.dumps(data)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(('127.0.0.1', 65432))
s.sendall(serialized)
# Servidor
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('127.0.0.1', 65432))
s.listen()
conn, addr = s.accept()
data = conn.recv(4096)
obj = pickle.loads(data)
print(f"Objeto recebido: {obj}")
Usando struct para dados binários
Para enviar números e bytes de forma eficiente:
import socket
import struct
# Empacotando dados: inteiro (4 bytes) + float (4 bytes) + string
valor_int = 42
valor_float = 3.14
string = b"exemplo"
# Formato: ! (network byte order), i (int), f (float), 7s (string de 7 bytes)
packed = struct.pack('!if7s', valor_int, valor_float, string)
# Enviando
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(('127.0.0.1', 65432))
s.sendall(packed)
# Recebendo e desempacotando
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('127.0.0.1', 65432))
s.listen()
conn, _ = s.accept()
data = conn.recv(4096)
int_val, float_val, str_val = struct.unpack('!if7s', data)
print(f"Int: {int_val}, Float: {float_val}, String: {str_val.decode()}")
Protocolo de comprimento fixo
Para evitar problemas de fragmentação, envie o tamanho antes dos dados:
def send_msg(sock, msg):
# Envia primeiro o tamanho da mensagem (4 bytes)
msg_len = len(msg)
sock.sendall(struct.pack('!I', msg_len))
sock.sendall(msg)
def recv_msg(sock):
# Recebe primeiro o tamanho
raw_len = recv_all(sock, 4)
if not raw_len:
return None
msg_len = struct.unpack('!I', raw_len)[0]
return recv_all(sock, msg_len)
def recv_all(sock, n):
data = bytearray()
while len(data) < n:
packet = sock.recv(n - len(data))
if not packet:
return None
data.extend(packet)
return bytes(data)
8. Boas Práticas e Considerações Finais
Segurança básica: Nunca envie dados sensíveis em texto claro. Use SSL/TLS para criptografar a comunicação:
import ssl
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(certfile="server.crt", keyfile="server.key")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.bind(('127.0.0.1', 65432))
sock.listen()
with context.wrap_socket(sock, server_side=True) as ssock:
conn, addr = ssock.accept()
# comunicação criptografada
Sockets Unix vs Rede: Sockets Unix (AF_UNIX) são mais rápidos para comunicação local, enquanto AF_INET permite comunicação em rede.
Aprofundamento: Para aplicações de alto desempenho, considere asyncio para sockets assíncronos ou bibliotecas como zeroconf para descoberta de serviços em rede.
Sockets são fundamentais para qualquer desenvolvedor Python que trabalhe com redes. Com os conceitos apresentados, você pode construir desde simples ferramentas de chat até servidores web e sistemas distribuídos complexos.
Referências
- Documentação oficial do módulo socket — Referência completa da API de sockets em Python, incluindo todos os métodos e constantes disponíveis.
- Real Python: Socket Programming in Python — Tutorial prático e aprofundado sobre programação com sockets, com exemplos de cliente e servidor TCP completos.
- Python Documentation: socketserver — Documentação oficial do módulo socketserver para criação de servidores concorrentes e simplificados.
- GeeksforGeeks: Socket Programming in Python — Guia completo com exemplos práticos de sockets TCP e UDP, incluindo tratamento de múltiplas conexões.
- Python Documentation: select — Documentação oficial do módulo select para monitoramento de múltiplos sockets e operações de I/O assíncronas.
- DigitalOcean: Python Socket Programming Tutorial — Tutorial passo a passo com exemplos de cliente e servidor, incluindo tratamento de erros e timeouts.