Introdução
UDP (User Datagram Protocol) é um protocolo de transporte leve e sem conexão. Diferente do TCP, o UDP não garante entrega, ordem ou integridade dos dados, mas oferece menor latência e overhead. É ideal para aplicações de tempo real como jogos, streaming de vídeo e DNS.
Nesta receita, você aprenderá a criar sockets UDP em Zig para enviar e receber datagramas.
Pré-requisitos
- Zig instalado (versão 0.13+). Veja o guia de instalação
- Conhecimento básico de Zig. Consulte a introdução ao Zig
Servidor UDP Básico
Um servidor UDP escuta em uma porta e recebe datagramas de qualquer cliente:
const std = @import("std");
const net = std.net;
const posix = std.posix;
pub fn main() !void {
// Criar socket UDP
const sock = try posix.socket(posix.AF.INET, posix.SOCK.DGRAM, 0);
defer posix.close(sock);
// Vincular à porta 9000
const addr = net.Address.initIp4(.{ 0, 0, 0, 0 }, 9000);
try posix.bind(sock, &addr.any, addr.getOsSockLen());
std.debug.print("Servidor UDP escutando na porta 9000...\n", .{});
var buffer: [1024]u8 = undefined;
while (true) {
var src_addr: posix.sockaddr = undefined;
var addr_len: posix.socklen_t = @sizeOf(posix.sockaddr);
// Receber datagrama
const bytes_received = try posix.recvfrom(
sock,
&buffer,
0,
&src_addr,
&addr_len,
);
const sender = net.Address.initPosix(@alignCast(&src_addr));
std.debug.print("Recebido de {}: {s}\n", .{ sender, buffer[0..bytes_received] });
// Enviar resposta de volta ao remetente
const response = "ACK";
_ = try posix.sendto(
sock,
response,
0,
&src_addr,
addr_len,
);
}
}
Cliente UDP Básico
O cliente envia datagramas ao servidor e recebe respostas:
const std = @import("std");
const net = std.net;
const posix = std.posix;
pub fn main() !void {
// Criar socket UDP
const sock = try posix.socket(posix.AF.INET, posix.SOCK.DGRAM, 0);
defer posix.close(sock);
// Endereço do servidor
const server_addr = net.Address.initIp4(.{ 127, 0, 0, 1 }, 9000);
// Enviar datagrama
const message = "Olá, servidor UDP!";
_ = try posix.sendto(
sock,
message,
0,
&server_addr.any,
server_addr.getOsSockLen(),
);
std.debug.print("Enviado: {s}\n", .{message});
// Receber resposta
var buffer: [1024]u8 = undefined;
var src_addr: posix.sockaddr = undefined;
var addr_len: posix.socklen_t = @sizeOf(posix.sockaddr);
const bytes_received = try posix.recvfrom(
sock,
&buffer,
0,
&src_addr,
&addr_len,
);
std.debug.print("Resposta: {s}\n", .{buffer[0..bytes_received]});
}
Saída esperada
Cliente:
Enviado: Olá, servidor UDP!
Resposta: ACK
Servidor:
Servidor UDP escutando na porta 9000...
Recebido de 127.0.0.1:45678: Olá, servidor UDP!
UDP Multicast
Multicast permite enviar dados para múltiplos receptores simultaneamente:
const std = @import("std");
const net = std.net;
const posix = std.posix;
/// Receptor multicast
pub fn multicastReceiver() !void {
const sock = try posix.socket(posix.AF.INET, posix.SOCK.DGRAM, 0);
defer posix.close(sock);
// Permitir reutilização do endereço
try posix.setsockopt(sock, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1)));
// Vincular à porta multicast
const bind_addr = net.Address.initIp4(.{ 0, 0, 0, 0 }, 5000);
try posix.bind(sock, &bind_addr.any, bind_addr.getOsSockLen());
// Entrar no grupo multicast 239.0.0.1
const mreq = extern struct {
multiaddr: [4]u8,
interface: [4]u8,
}{
.multiaddr = .{ 239, 0, 0, 1 },
.interface = .{ 0, 0, 0, 0 },
};
try posix.setsockopt(sock, posix.IPPROTO.IP, 12, // IP_ADD_MEMBERSHIP
std.mem.asBytes(&mreq));
std.debug.print("Escutando multicast em 239.0.0.1:5000...\n", .{});
var buffer: [1024]u8 = undefined;
while (true) {
var src_addr: posix.sockaddr = undefined;
var addr_len: posix.socklen_t = @sizeOf(posix.sockaddr);
const n = try posix.recvfrom(sock, &buffer, 0, &src_addr, &addr_len);
std.debug.print("Multicast: {s}\n", .{buffer[0..n]});
}
}
/// Emissor multicast
pub fn multicastSender() !void {
const sock = try posix.socket(posix.AF.INET, posix.SOCK.DGRAM, 0);
defer posix.close(sock);
const dest = net.Address.initIp4(.{ 239, 0, 0, 1 }, 5000);
const message = "Broadcast para todos!";
_ = try posix.sendto(
sock,
message,
0,
&dest.any,
dest.getOsSockLen(),
);
std.debug.print("Mensagem multicast enviada.\n", .{});
}
UDP com Timeout
Adicione timeouts para evitar bloqueio indefinido ao esperar respostas:
const std = @import("std");
const net = std.net;
const posix = std.posix;
pub fn udpWithTimeout() !void {
const sock = try posix.socket(posix.AF.INET, posix.SOCK.DGRAM, 0);
defer posix.close(sock);
// Configurar timeout de 3 segundos
const timeout = posix.timeval{
.sec = 3,
.usec = 0,
};
try posix.setsockopt(
sock,
posix.SOL.SOCKET,
posix.SO.RCVTIMEO,
&std.mem.toBytes(timeout),
);
const server = net.Address.initIp4(.{ 127, 0, 0, 1 }, 9000);
_ = try posix.sendto(sock, "ping", 0, &server.any, server.getOsSockLen());
var buffer: [1024]u8 = undefined;
var src_addr: posix.sockaddr = undefined;
var addr_len: posix.socklen_t = @sizeOf(posix.sockaddr);
_ = posix.recvfrom(sock, &buffer, 0, &src_addr, &addr_len) catch |err| {
if (err == error.WouldBlock) {
std.debug.print("Timeout: nenhuma resposta em 3 segundos.\n", .{});
return;
}
return err;
};
}
TCP vs UDP: Quando Usar Cada Um
| Característica | TCP | UDP |
|---|---|---|
| Conexão | Orientado a conexão | Sem conexão |
| Confiabilidade | Entrega garantida | Sem garantia |
| Ordem | Ordenado | Não ordenado |
| Overhead | Maior | Menor |
| Uso ideal | HTTP, FTP, e-mail | Jogos, DNS, streaming |
Dicas e Boas Práticas
Tamanho máximo do datagrama: O tamanho máximo prático de um datagrama UDP é 65.507 bytes, mas para evitar fragmentação IP, mantenha abaixo de 1.472 bytes (1.500 MTU - headers).
UDP não é confiável: Datagramas podem ser perdidos, duplicados ou chegar fora de ordem. Implemente confirmação (ACK) se necessário.
Use timeouts: Sempre configure timeouts ao esperar respostas UDP.
Gerencie memória: Use allocators do Zig adequadamente para buffers dinâmicos.
Receitas Relacionadas
- Como criar um cliente TCP em Zig - Alternativa confiável com TCP
- Como criar um servidor TCP em Zig - Servidor TCP
- Como fazer lookup DNS em Zig - DNS usa UDP
- Como tratar erros em Zig - Tratamento de erros de rede