Perguntas de Entrevista sobre Networking em Zig

Perguntas de Entrevista sobre Networking em Zig

Programação de rede é um tema frequente em entrevistas para posições de sistemas, backend e infraestrutura. O Zig oferece APIs de rede na stdlib (std.net, std.http) que expõem detalhes do sistema sem abstrações excessivas. Este guia cobre perguntas práticas com implementações idiomáticas em Zig.

Perguntas e Respostas

1. Como funciona o modelo de sockets no Zig?

O Zig fornece wrappers sobre sockets POSIX via std.net e std.posix. O modelo segue o fluxo clássico: criar socket, bind, listen, accept (servidor) ou connect (cliente). A diferença do Zig é que erros são explícitos via unions de erro e o allocator é explicitamente gerenciado.

const std = @import("std");

// Servidor TCP básico
pub fn servidorSimples() !void {
    // Cria socket de escuta
    const endereco = try std.net.Address.resolveIp("127.0.0.1", 8080);
    var servidor = try endereco.listen(.{
        .reuse_address = true,
    });
    defer servidor.deinit();

    std.debug.print("Servidor ouvindo em 127.0.0.1:8080\n", .{});

    // Aceita uma conexão
    const conexao = try servidor.accept();
    defer conexao.stream.close();

    // Lê dados do cliente
    var buf: [1024]u8 = undefined;
    const n = try conexao.stream.read(&buf);
    std.debug.print("Recebido: {s}\n", .{buf[0..n]});

    // Responde
    _ = try conexao.stream.write("HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nOK\n");
}

A key insight para entrevistas: Zig não esconde a complexidade dos sockets, mas adiciona segurança de tipos e tratamento de erros obrigatório.

2. Implemente um cliente TCP que conecta e envia dados.

const std = @import("std");

pub fn clienteTcp(host: []const u8, porta: u16, mensagem: []const u8) ![]u8 {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Resolve endereço e conecta
    const stream = try std.net.tcpConnectToHost(allocator, host, porta);
    defer stream.close();

    // Envia dados
    _ = try stream.write(mensagem);

    // Lê resposta
    var buf: [4096]u8 = undefined;
    const n = try stream.read(&buf);
    if (n == 0) return error.ConnectionClosed;

    // Copia para retornar (caller libera)
    const resultado = try allocator.alloc(u8, n);
    @memcpy(resultado, buf[0..n]);
    return resultado;
}

3. Como fazer requisições HTTP com std.http?

const std = @import("std");

pub fn httpGet(allocator: std.mem.Allocator, url_str: []const u8) ![]u8 {
    const uri = try std.Uri.parse(url_str);

    var client = std.http.Client{ .allocator = allocator };
    defer client.deinit();

    // Buffer para headers
    var header_buf: [4096]u8 = undefined;

    // Faz a requisição
    var req = try client.open(.GET, uri, .{
        .server_header_buffer = &header_buf,
    });
    defer req.deinit();

    try req.send();
    try req.wait();

    // Verifica status
    if (req.status != .ok) {
        std.debug.print("HTTP Error: {}\n", .{req.status});
        return error.HttpError;
    }

    // Lê o body
    const body = try req.reader().readAllAlloc(allocator, 1024 * 1024);
    return body;
}

4. Qual a diferença entre TCP e UDP no contexto do Zig?

TCP (std.net.Stream) fornece conexão confiável e ordenada. UDP (std.posix.socket) é sem conexão e não garante entrega. No Zig, TCP usa a abstração Stream com read/write, enquanto UDP usa sendto/recvfrom diretamente.

const std = @import("std");

// Servidor UDP simples
pub fn servidorUdp() !void {
    const sock = try std.posix.socket(
        std.posix.AF.INET,
        std.posix.SOCK.DGRAM,
        0,
    );
    defer std.posix.close(sock);

    const addr = std.net.Address.initIp4(.{ 127, 0, 0, 1 }, 9090);
    try std.posix.bind(sock, &addr.any, addr.getOsSockLen());

    var buf: [512]u8 = undefined;
    var src_addr: std.posix.sockaddr = undefined;
    var addr_len: std.posix.socklen_t = @sizeOf(std.posix.sockaddr);

    const n = try std.posix.recvfrom(sock, &buf, 0, &src_addr, &addr_len);
    std.debug.print("UDP recebido ({d} bytes): {s}\n", .{ n, buf[0..n] });
}

5. Como implementar um servidor HTTP simples?

const std = @import("std");

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

    const endereco = try std.net.Address.resolveIp("127.0.0.1", 8080);
    var servidor = try endereco.listen(.{ .reuse_address = true });
    defer servidor.deinit();

    std.debug.print("HTTP server em http://127.0.0.1:8080\n", .{});

    while (true) {
        const conexao = try servidor.accept();
        defer conexao.stream.close();

        // Lê requisição
        var buf: [4096]u8 = undefined;
        const n = try conexao.stream.read(&buf);
        if (n == 0) continue;

        const requisicao = buf[0..n];

        // Extrai método e caminho (parsing simples)
        var iter = std.mem.splitScalar(u8, requisicao, ' ');
        const metodo = iter.next() orelse continue;
        const caminho = iter.next() orelse continue;

        std.debug.print("{s} {s}\n", .{ metodo, caminho });

        // Roteamento simples
        const body = if (std.mem.eql(u8, caminho, "/"))
            "<h1>Bem-vindo ao servidor Zig!</h1>"
        else if (std.mem.eql(u8, caminho, "/api/status"))
            "{\"status\": \"ok\", \"lang\": \"zig\"}"
        else
            "<h1>404 - Não encontrado</h1>";

        const content_type = if (std.mem.startsWith(u8, caminho, "/api"))
            "application/json"
        else
            "text/html";

        // Envia resposta HTTP
        var response_buf: [4096]u8 = undefined;
        const response = std.fmt.bufPrint(&response_buf,
            "HTTP/1.1 200 OK\r\nContent-Type: {s}\r\nContent-Length: {d}\r\nConnection: close\r\n\r\n{s}",
            .{ content_type, body.len, body },
        ) catch continue;

        _ = try conexao.stream.write(response);
    }
}

6. Como tratar timeouts em conexões de rede?

No Zig, timeouts podem ser configurados via opções do socket. A stdlib não abstrai isso diretamente — use std.posix.setsockopt para SO_RCVTIMEO e SO_SNDTIMEO.

const std = @import("std");

pub fn conectarComTimeout(host: []const u8, porta: u16, timeout_ms: u32) !std.net.Stream {
    const allocator = std.heap.page_allocator;
    const stream = try std.net.tcpConnectToHost(allocator, host, porta);

    // Define timeout de leitura
    const timeout = std.posix.timeval{
        .sec = @intCast(timeout_ms / 1000),
        .usec = @intCast((timeout_ms % 1000) * 1000),
    };

    try std.posix.setsockopt(stream.handle, std.posix.SOL.SOCKET, std.posix.SO.RCVTIMEO, std.mem.asBytes(&timeout));
    try std.posix.setsockopt(stream.handle, std.posix.SOL.SOCKET, std.posix.SO.SNDTIMEO, std.mem.asBytes(&timeout));

    return stream;
}

7. Como funciona a resolução de DNS no Zig?

const std = @import("std");

pub fn resolverDns(allocator: std.mem.Allocator, hostname: []const u8) !void {
    const stdout = std.io.getStdOut().writer();

    // Resolve hostname para endereços
    const lista = try std.net.getAddressList(allocator, hostname, 80);
    defer lista.deinit();

    try stdout.print("Resolvendo: {s}\n", .{hostname});
    for (lista.addrs) |addr| {
        try stdout.print("  {}\n", .{addr});
    }
}

8. Implemente um protocolo simples de mensagens sobre TCP.

Um protocolo com prefixo de tamanho (length-prefixed) é robusto para comunicação TCP:

const std = @import("std");

const Protocolo = struct {
    /// Envia mensagem com prefixo de tamanho (4 bytes big-endian).
    pub fn enviar(stream: std.net.Stream, dados: []const u8) !void {
        // Envia tamanho (4 bytes)
        const tamanho: u32 = @intCast(dados.len);
        const header = std.mem.toBytes(std.mem.nativeToBig(u32, tamanho));
        _ = try stream.write(&header);
        // Envia dados
        _ = try stream.write(dados);
    }

    /// Recebe mensagem com prefixo de tamanho.
    pub fn receber(stream: std.net.Stream, buf: []u8) ![]u8 {
        // Lê tamanho
        var header: [4]u8 = undefined;
        const h_read = try stream.read(&header);
        if (h_read != 4) return error.ProtocolError;

        const tamanho = std.mem.bigToNative(u32, std.mem.bytesToValue(u32, &header));
        if (tamanho > buf.len) return error.MessageTooLarge;

        // Lê dados
        var total: usize = 0;
        while (total < tamanho) {
            const n = try stream.read(buf[total..tamanho]);
            if (n == 0) return error.ConnectionClosed;
            total += n;
        }

        return buf[0..tamanho];
    }
};

9. Quais são os padrões comuns de error handling em código de rede?

const std = @import("std");

fn tratarConexao(stream: std.net.Stream) void {
    defer stream.close();

    var buf: [4096]u8 = undefined;
    while (true) {
        const n = stream.read(&buf) catch |err| switch (err) {
            error.ConnectionResetByPeer => {
                std.debug.print("Conexão resetada pelo peer\n", .{});
                return;
            },
            error.ConnectionTimedOut => {
                std.debug.print("Timeout na conexão\n", .{});
                return;
            },
            error.BrokenPipe => {
                std.debug.print("Pipe quebrado\n", .{});
                return;
            },
            else => {
                std.debug.print("Erro de rede: {}\n", .{err});
                return;
            },
        };

        if (n == 0) {
            std.debug.print("Conexão fechada pelo cliente\n", .{});
            return;
        }

        // Processa dados...
        _ = stream.write(buf[0..n]) catch return;
    }
}

10. Como implementar um pool de conexões?

const std = @import("std");
const Allocator = std.mem.Allocator;

pub const ConnectionPool = struct {
    const Self = @This();

    disponíveis: std.ArrayList(std.net.Stream),
    em_uso: usize,
    max_conexoes: usize,
    host: []const u8,
    porta: u16,
    allocator: Allocator,

    pub fn init(allocator: Allocator, host: []const u8, porta: u16, max: usize) Self {
        return .{
            .disponíveis = std.ArrayList(std.net.Stream).init(allocator),
            .em_uso = 0,
            .max_conexoes = max,
            .host = host,
            .porta = porta,
            .allocator = allocator,
        };
    }

    pub fn obter(self: *Self) !std.net.Stream {
        // Tenta reutilizar conexão existente
        if (self.disponíveis.items.len > 0) {
            self.em_uso += 1;
            return self.disponíveis.pop();
        }

        // Cria nova se não atingiu o limite
        if (self.em_uso < self.max_conexoes) {
            const stream = try std.net.tcpConnectToHost(self.allocator, self.host, self.porta);
            self.em_uso += 1;
            return stream;
        }

        return error.PoolExhausted;
    }

    pub fn devolver(self: *Self, stream: std.net.Stream) !void {
        try self.disponíveis.append(stream);
        self.em_uso -= 1;
    }

    pub fn deinit(self: *Self) void {
        for (self.disponíveis.items) |stream| {
            stream.close();
        }
        self.disponíveis.deinit();
    }
};

11. Como fazer I/O não-bloqueante no Zig?

O Zig suporta I/O assíncrono via std.posix com flags como O_NONBLOCK e poll/epoll. A abordagem moderna usa std.io.poll para multiplexar conexões:

const std = @import("std");

// Configurar socket como não-bloqueante
pub fn tornarNaoBloqueante(fd: std.posix.fd_t) !void {
    const flags = try std.posix.fcntl(fd, .F_GETFL, 0);
    _ = try std.posix.fcntl(fd, .F_SETFL, flags | std.posix.O.NONBLOCK);
}

12. Explique como serializar e deserializar dados para transmissão em rede.

A serialização para rede geralmente usa big-endian (network byte order). O Zig fornece std.mem.nativeToBig e std.mem.bigToNative:

const std = @import("std");

const Pacote = packed struct {
    tipo: u8,
    sequencia: u16,
    tamanho: u32,
};

fn serializarPacote(pacote: Pacote) [7]u8 {
    var buf: [7]u8 = undefined;
    buf[0] = pacote.tipo;
    std.mem.writeInt(u16, buf[1..3], pacote.sequencia, .big);
    std.mem.writeInt(u32, buf[3..7], pacote.tamanho, .big);
    return buf;
}

fn deserializarPacote(buf: [7]u8) Pacote {
    return .{
        .tipo = buf[0],
        .sequencia = std.mem.readInt(u16, buf[1..3], .big),
        .tamanho = std.mem.readInt(u32, buf[3..7], .big),
    };
}

Recursos Relacionados

Continue aprendendo Zig

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