9.12. Sockets UDP¶
O tráfego UDP em Python é enviado e recebido com dois métodos em um socket de datagrama: sendto() para disparar um datagrama a um destino escolhido e recvfrom() para receber um datagrama e descobrir de onde ele veio. Cada chamada move uma mensagem autocontida; não há estado de conexão.
9.12.1. Enviando um datagrama¶
O envio UDP mais simples é uma linha de Python sobre um construtor de socket:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(b"hello", ("192.168.1.20", 9000))
s.close()
Isso envia b"hello" para a porta 9000 em 192.168.1.20 e segue adiante. O MicroPython escolhe uma porta de origem efêmera; o script não precisa vincular nada.
Enviar a mesma carga útil para muitos destinos é apenas um laço – o socket é reutilizável entre os envios e não há conexão a estabelecer:
targets = [
("192.168.1.20", 9000),
("192.168.1.21", 9000),
("192.168.1.22", 9000),
]
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
for addr in targets:
s.sendto(b"hello", addr)
9.12.2. Recebendo um datagrama¶
Para receber datagramas, o socket precisa reivindicar uma porta conhecida que os remetentes usarão como destino. É essa a chamada bind()
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("0.0.0.0", 9000))
while True:
data, src = s.recvfrom(1024)
print("from", src, "got", data)
O endereço "0.0.0.0" significa “toda interface IPv4 na câmera” – seja qual for a interface Wi-Fi ou Ethernet que traz os pacotes, a porta 9000 pertence a esse socket.
O argumento 1024 de recvfrom() é o número máximo de bytes a ler para o buffer retornado. Datagramas UDP acima desse tamanho serão truncados; escolha o valor para corresponder ao maior datagrama que a aplicação espera.
recvfrom() retorna (data, src): os bytes recebidos e o endereço do remetente. O endereço do remetente é para onde responder, o que facilita escrever um pequeno protocolo de requisição/resposta:
while True:
request, src = s.recvfrom(1024)
if request == b"ping":
s.sendto(b"pong", src)
Por padrão, recvfrom() bloqueia até que um datagrama chegue. Os padrões para fazê-lo não bloquear – timeouts, sockets não bloqueantes, asyncio – estão em Sockets com asyncio.
9.12.3. Uma requisição e uma resposta¶
Dois scripts curtos: um envia uma requisição, o outro recebe e responde.
O receptor:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("0.0.0.0", 9000))
while True:
req, src = s.recvfrom(64)
print("got", req, "from", src)
s.sendto(b"ack: " + req, src)
O remetente:
import socket
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.settimeout(2.0) # 2 s reply window
s.sendto(b"ping", ("192.168.1.20", 9000))
try:
reply, _ = s.recvfrom(64)
print("reply:", reply)
except OSError:
print("no reply in 2 s -- packet lost?")
Algumas coisas que vale a pena observar no remetente:
Nenhum
bind()e nenhumconnect(). Clientes UDP apenas enviam.settimeout()impõe um prazo à chamada de recebimento. Se nenhuma resposta chegar em dois segundos, a chamada levantaOSErrorem vez de bloquear para sempre – uma forma natural de detectar um pacote perdido.O bloco
withfecha o socket automaticamente.
9.12.4. Limites de tamanho de datagrama¶
Em teoria, datagramas UDP podem ter até cerca de 64 KB, mas o limite prático é muito menor. Cada enlace no caminho entre o remetente e o receptor tem uma Maximum Transmission Unit (MTU) – o maior bloco único de bytes que aquele enlace pode transportar em um quadro. Ethernet e Wi-Fi limitam isso a cerca de 1500 bytes, e quase todo caminho na internet remonta a esse limite em algum ponto.
Quando um datagrama excede a MTU de um enlace que ele precisa atravessar, a camada de rede o divide em fragmentos menores e os remonta no destino. O próprio UDP nunca enxerga a divisão, mas os fragmentos têm várias propriedades inconvenientes:
Se qualquer fragmento for perdido, o datagrama inteiro é descartado no receptor – não há retransmissão por fragmento. A probabilidade de perda cresce com a contagem de fragmentos.
Algumas redes e firewalls descartam pacotes fragmentados por completo, tratando-os como suspeitos.
A remontagem custa memória no receptor, que em um microcontrolador é escassa.
A regra prática na câmera: mantenha as mensagens UDP bem abaixo de 1500 bytes. Cerca de 1400 bytes deixa espaço para os cabeçalhos IP e UDP, qualquer sobrecarga de tunelamento que o caminho acrescente e pequenas variações de MTU entre enlaces Ethernet, Wi-Fi e VPN. Aplicações que precisam enviar mais do que isso devem fragmentar os dados na camada de aplicação ou mudar para TCP, que lida com a divisão e a remontagem automaticamente.
9.12.5. Armadilhas comuns¶
Esquecer que o UDP pode perder pacotes. Código que funciona perfeitamente em uma rede local tranquila às vezes falha de maneiras sutis em uma rede mais movimentada ou mais ampla. Sempre projete considerando a possibilidade de a mensagem não ter chegado.
Receptor não vinculado antes de o remetente enviar. Um datagrama enviado a uma porta em que ninguém está escutando é descartado silenciosamente. Inicie o receptor primeiro.
Enviar um datagrama maior que a MTU do caminho. Veja a seção anterior – mantenha as mensagens abaixo de ~1400 bytes.
Os padrões acima cobrem quase todo uso de UDP que a câmera precisa. A próxima página faz o equivalente para o TCP.