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
- std.net — Módulo de rede da stdlib
- std.http — Cliente e servidor HTTP
- std.io — Streams de I/O
- Perguntas de Entrevista: Concorrência
- Perguntas de Entrevista: Performance
- Desafio de Código: Sistemas