std.net em Zig — Referência e Exemplos

std.net — Networking de Baixo Nível

O módulo std.net fornece primitivas de rede para comunicação via sockets TCP e UDP. Ele encapsula as APIs de sockets do sistema operacional em uma interface idiomática do Zig, com tratamento de erros explícito e suporte a IPv4 e IPv6. É a base sobre a qual o módulo std.http é construído.

Visão Geral

const std = @import("std");
const net = std.net;

O módulo é organizado em torno de três conceitos principais:

  1. Address — Representa um endereço de rede (IP + porta)
  2. Stream — Conexão TCP bidirecional (leitura e escrita)
  3. Server — Socket de escuta que aceita conexões

Tipos Principais

net.Address

pub const Address = union(enum) {
    ipv4: std.posix.sockaddr.in,
    ipv6: std.posix.sockaddr.in6,
    un: std.posix.sockaddr.un,  // Unix domain socket
};

net.Stream

pub const Stream = struct {
    handle: std.posix.socket_t,

    pub fn read(self: Stream, buf: []u8) !usize
    pub fn write(self: Stream, buf: []const u8) !usize
    pub fn close(self: Stream) void

    // Implementa interfaces de Reader e Writer
    pub fn reader(self: Stream) Reader
    pub fn writer(self: Stream) Writer
};

net.Server

pub const Server = struct {
    pub fn accept(self: *Server) !Connection
    pub fn deinit(self: *Server) void

    pub const Connection = struct {
        stream: Stream,
        address: Address,
    };
};

Funções Principais

// Resolução de endereço
pub fn Address.parseIp4(ip: []const u8, port: u16) !Address
pub fn Address.parseIp6(ip: []const u8, port: u16) !Address
pub fn Address.resolveIp(name: []const u8, port: u16) !Address

// Conexão TCP
pub fn tcpConnectToHost(allocator: Allocator, host: []const u8, port: u16) !Stream
pub fn tcpConnectToAddress(address: Address) !Stream

// Servidor TCP
pub fn Address.listen(address: Address, options: ListenOptions) !Server

Exemplo 1: Cliente TCP Simples

const std = @import("std");
const net = std.net;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const stdout = std.io.getStdOut().writer();

    // Conecta a um servidor
    const stream = net.tcpConnectToHost(allocator, "example.com", 80) catch |err| {
        try stdout.print("Erro de conexão: {}\n", .{err});
        return;
    };
    defer stream.close();

    try stdout.writeAll("Conectado a example.com:80\n");

    // Envia requisição HTTP simples
    const request =
        "GET / HTTP/1.1\r\n" ++
        "Host: example.com\r\n" ++
        "Connection: close\r\n" ++
        "\r\n";

    _ = try stream.write(request);

    // Lê resposta
    var buf: [4096]u8 = undefined;
    var total: usize = 0;
    while (true) {
        const n = stream.read(&buf) catch break;
        if (n == 0) break;
        total += n;
    }

    try stdout.print("Bytes recebidos: {d}\n", .{total});
}

Exemplo 2: Servidor Echo TCP

const std = @import("std");
const net = std.net;

fn handleClient(connection: net.Server.Connection) void {
    defer connection.stream.close();

    const reader = connection.stream.reader();
    const writer = connection.stream.writer();

    // Echo: lê e reenvia
    var buf: [1024]u8 = undefined;
    while (true) {
        const n = reader.read(&buf) catch break;
        if (n == 0) break;

        writer.writeAll(buf[0..n]) catch break;
    }

    std.debug.print("Cliente desconectou: {}\n", .{connection.address});
}

pub fn main() !void {
    const addr = try net.Address.parseIp4("127.0.0.1", 9000);

    var server = try addr.listen(.{
        .reuse_address = true,
    });
    defer server.deinit();

    std.debug.print("Servidor echo escutando em 127.0.0.1:9000\n", .{});

    // Aceita conexões em loop
    while (true) {
        const connection = server.accept() catch |err| {
            std.debug.print("Erro ao aceitar: {}\n", .{err});
            continue;
        };

        std.debug.print("Nova conexão de: {}\n", .{connection.address});

        // Em produção, use threads ou async para múltiplos clientes
        handleClient(connection);
    }
}

Exemplo 3: Resolução de Endereços

const std = @import("std");
const net = std.net;

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    // Parse de endereço IPv4
    const addr4 = try net.Address.parseIp4("192.168.1.100", 8080);
    try stdout.print("IPv4: {}\n", .{addr4});

    // Parse de endereço IPv6
    const addr6 = try net.Address.parseIp6("::1", 8080);
    try stdout.print("IPv6: {}\n", .{addr6});

    // Resolução de hostname
    const resolved = net.Address.resolveIp("localhost", 3000) catch |err| {
        try stdout.print("Erro na resolução: {}\n", .{err});
        return;
    };
    try stdout.print("localhost: {}\n", .{resolved});

    // Comparação de endereços
    const a = try net.Address.parseIp4("127.0.0.1", 80);
    const b = try net.Address.parseIp4("127.0.0.1", 80);
    const c = try net.Address.parseIp4("127.0.0.1", 443);

    try stdout.print("a == b: {}\n", .{a.eql(b)});
    try stdout.print("a == c: {}\n", .{a.eql(c)});

    // Porta
    try stdout.print("Porta de a: {d}\n", .{a.getPort()});
}

Padrões Comuns

Timeout de Conexão

// Configura timeout via opções do socket
const stream = try net.tcpConnectToHost(allocator, "host", 80);
// Use std.posix.setsockopt para configurar SO_RCVTIMEO / SO_SNDTIMEO

Servidor Concorrente (com Threads)

const connection = try server.accept();
const thread = try std.Thread.spawn(.{}, handleClient, .{connection});
thread.detach();

Módulos Relacionados

Tutoriais e Receitas Relacionados

Continue aprendendo Zig

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