Como Usar Sockets UDP em Zig

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

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ísticaTCPUDP
ConexãoOrientado a conexãoSem conexão
ConfiabilidadeEntrega garantidaSem garantia
OrdemOrdenadoNão ordenado
OverheadMaiorMenor
Uso idealHTTP, FTP, e-mailJogos, DNS, streaming

Dicas e Boas Práticas

  1. 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).

  2. UDP não é confiável: Datagramas podem ser perdidos, duplicados ou chegar fora de ordem. Implemente confirmação (ACK) se necessário.

  3. Use timeouts: Sempre configure timeouts ao esperar respostas UDP.

  4. Gerencie memória: Use allocators do Zig adequadamente para buffers dinâmicos.

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.