9.12. Sockets UDP¶
O tráfego UDP em Python é enviado e recebido com dois métodos num socket de datagramas: sendto() para enviar um datagrama para um destino escolhido, e recvfrom() para receber um datagrama e saber de onde veio. Cada chamada move uma mensagem autónoma; não existe estado de ligação.
9.12.1. Enviar 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()
Isto envia b"hello" para a porta 9000 em 192.168.1.20 e termina. O MicroPython escolhe uma porta de origem efémera; o script não precisa de fazer bind a nada.
Enviar o mesmo payload para vários destinos é apenas um ciclo – o socket é reutilizável entre envios e não há nenhuma ligaçã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. Receber um datagrama¶
Para receber datagramas, o socket tem de reservar uma porta conhecida que os emissores utilizarão como destino. É isso que faz 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 «todas as interfaces IPv4 da câmara» – qualquer interface Wi-Fi ou Ethernet que receba pacotes, a porta 9000 pertence a este socket.
O argumento 1024 de recvfrom() é o número máximo de bytes a ler para o buffer devolvido. Os datagramas UDP acima deste tamanho serão truncados; escolha o valor de acordo com o maior datagrama que a aplicação espera receber.
recvfrom() devolve (data, src): os bytes recebidos e o endereço do remetente. O endereço do remetente é para onde responder, facilitando a escrita de um pequeno protocolo de pedido/resposta:
while True:
request, src = s.recvfrom(1024)
if request == b"ping":
s.sendto(b"pong", src)
Por omissão, recvfrom() bloqueia até que um datagrama chegue. Os padrões para evitar o bloqueio – timeouts, sockets não bloqueantes, asyncio – estão em Sockets com asyncio.
9.12.3. Um pedido e uma resposta¶
Dois scripts curtos: um envia um pedido, o outro recebe e responde.
O recetor:
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 emissor:
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?")
Alguns pontos a destacar no emissor:
Sem
bind()e semconnect(). Os clientes UDP simplesmente enviam.settimeout()define um prazo para a chamada de receção. Se não chegar nenhuma resposta em dois segundos, a chamada lançaOSErrorem vez de bloquear indefinidamente – uma forma natural de detetar um pacote perdido.O bloco
withfecha o socket automaticamente.
9.12.4. Limites de tamanho dos datagramas¶
Os datagramas UDP podem ter até cerca de 64 KB em teoria, mas o limite prático é muito menor. Cada ligação no caminho entre o emissor e o recetor tem uma Maximum Transmission Unit (MTU) – o maior bloco único de bytes que essa ligação consegue transportar num fotograma. Ethernet e Wi-Fi limitam ambos a cerca de 1500 bytes, e quase todos os caminhos na internet remontam a esse limite nalgum ponto.
Quando um datagrama excede a MTU de uma ligação que tem de atravessar, a camada de rede divide-o em fragmentos mais pequenos e remonta-os no destino. O próprio UDP nunca vê a divisão, mas os fragmentos têm várias propriedades inconvenientes:
Se qualquer um dos fragmentos se perder, o datagrama completo é descartado no recetor – não há retransmissão por fragmento. A probabilidade de perda aumenta com o número de fragmentos.
Algumas redes e firewalls descartam pacotes fragmentados por completo, tratando-os como suspeitos.
A remontagem consome memória no recetor, que num microcontrolador é escassa.
A regra prática na câmara: mantenha as mensagens UDP bem abaixo dos 1500 bytes. Cerca de 1400 bytes deixa margem para os cabeçalhos IP e UDP, qualquer sobrecarga de tunelamento que o caminho acrescente e pequenas variações de MTU entre ligações Ethernet, Wi-Fi e VPN. As aplicações que precisam de enviar mais do que isso devem ou fragmentar os dados na camada de aplicação ou mudar para TCP, que trata da divisão e remontagem automaticamente.
9.12.5. Armadilhas comuns¶
Esquecer que o UDP pode perder pacotes. Código que funciona perfeitamente numa rede local tranquila pode falhar de formas subtis numa rede mais movimentada ou mais ampla. Projete sempre tendo em conta a possibilidade de a mensagem não ter chegado.
Recetor não ligado antes de o emissor enviar. Um datagrama enviado para uma porta sem ninguém a ouvir é descartado silenciosamente. Inicie o recetor primeiro.
Enviar um datagrama maior do que a MTU do caminho. Consulte a secção anterior – mantenha as mensagens abaixo de ~1400 bytes.
Os padrões acima cobrem quase todos os casos de utilização de UDP na câmara. A próxima página faz o equivalente para TCP.