Networking e um pilar fundamental da programacao de sistemas moderna. Neste quarto artigo da serie, exploramos como Zig fornece acesso direto a sockets de rede, desde conexoes TCP/UDP simples ate sockets raw para protocolos customizados. A standard library do Zig oferece abstracoees seguras sem sacrificar o controle de baixo nivel que programadores de sistemas precisam.
Para uma introducao mais acessivel a networking, veja Networking e Sockets em Zig. Este artigo foca no nivel mais baixo.
Fundamentos: Sockets TCP
O protocolo TCP (Transmission Control Protocol) fornece comunicacao confiavel e orientada a conexao. E a base de HTTP, SSH, SMTP e muitos outros protocolos.
Servidor TCP Basico
const std = @import("std");
const net = std.net;
pub fn main() !void {
// Criar socket de escuta
const endereco = try net.Address.parseIp4("127.0.0.1", 8080);
var servidor = try net.StreamServer.init(.{
.reuse_address = true,
});
defer servidor.deinit();
try servidor.listen(endereco);
std.debug.print("Servidor TCP escutando em 127.0.0.1:8080\n", .{});
while (true) {
// Aceitar conexao
const conexao = try servidor.accept();
defer conexao.stream.close();
std.debug.print("Nova conexao de {}\n", .{conexao.address});
// Ler dados do cliente
var buffer: [1024]u8 = undefined;
const bytes = try conexao.stream.read(&buffer);
if (bytes > 0) {
std.debug.print("Recebido: {s}\n", .{buffer[0..bytes]});
// Enviar resposta
try conexao.stream.writeAll("HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nOla, Mundo!");
}
}
}
Cliente TCP
const std = @import("std");
const net = std.net;
pub fn main() !void {
// Conectar ao servidor
const stream = try net.tcpConnectToHost(
std.heap.page_allocator,
"127.0.0.1",
8080,
);
defer stream.close();
// Enviar requisicao HTTP
const requisicao =
"GET / HTTP/1.1\r\n" ++
"Host: 127.0.0.1\r\n" ++
"Connection: close\r\n" ++
"\r\n";
try stream.writeAll(requisicao);
// Ler resposta
var buffer: [4096]u8 = undefined;
var total: usize = 0;
while (true) {
const n = try stream.read(buffer[total..]);
if (n == 0) break;
total += n;
}
std.debug.print("Resposta:\n{s}\n", .{buffer[0..total]});
}
Sockets UDP
UDP (User Datagram Protocol) e um protocolo sem conexao, ideal para aplicacoes que precisam de baixa latencia e toleram perda de pacotes (jogos, streaming, DNS).
Servidor e Cliente UDP
const std = @import("std");
const net = std.net;
const posix = std.posix;
pub fn servidorUDP() !void {
const sock = try posix.socket(
posix.AF.INET,
posix.SOCK.DGRAM,
0,
);
defer posix.close(sock);
const endereco = net.Address.initIp4(.{ 127, 0, 0, 1 }, 9090);
try posix.bind(sock, &endereco.any, endereco.getOsSockLen());
std.debug.print("Servidor UDP escutando em 127.0.0.1:9090\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("Recebido {d} bytes: {s}\n", .{ n, buffer[0..n] });
// Ecoar de volta
_ = try posix.sendto(
sock,
buffer[0..n],
0,
&src_addr,
addr_len,
);
}
}
pub fn main() !void {
try servidorUDP();
}
Servidor TCP Concorrente
Para lidar com multiplos clientes simultaneamente, podemos usar threads:
const std = @import("std");
const net = std.net;
fn handleCliente(conexao: net.StreamServer.Connection) void {
defer conexao.stream.close();
var buffer: [4096]u8 = undefined;
while (true) {
const bytes = conexao.stream.read(&buffer) catch |err| {
std.debug.print("Erro de leitura: {}\n", .{err});
return;
};
if (bytes == 0) {
std.debug.print("Cliente {} desconectou\n", .{conexao.address});
return;
}
const dados = buffer[0..bytes];
std.debug.print("[{}] {s}", .{ conexao.address, dados });
// Ecoar dados de volta (echo server)
conexao.stream.writeAll(dados) catch |err| {
std.debug.print("Erro de escrita: {}\n", .{err});
return;
};
}
}
pub fn main() !void {
const endereco = try net.Address.parseIp4("0.0.0.0", 8080);
var servidor = try net.StreamServer.init(.{
.reuse_address = true,
});
defer servidor.deinit();
try servidor.listen(endereco);
std.debug.print("Echo server rodando em 0.0.0.0:8080\n", .{});
while (true) {
const conexao = servidor.accept() catch |err| {
std.debug.print("Erro ao aceitar: {}\n", .{err});
continue;
};
// Criar thread para cada cliente
_ = std.Thread.spawn(.{}, handleCliente, .{conexao}) catch |err| {
std.debug.print("Erro ao criar thread: {}\n", .{err});
conexao.stream.close();
};
}
}
Sockets Raw
Sockets raw permitem enviar e receber pacotes de rede no nivel mais baixo, dando acesso direto aos cabecalhos IP e protocolos customizados. Geralmente requerem privilegios de root.
Construindo um Pacote ICMP (Ping)
const std = @import("std");
const posix = std.posix;
const IcmpHeader = packed struct {
tipo: u8,
codigo: u8,
checksum: u16,
identificador: u16,
sequencia: u16,
};
fn calcularChecksum(data: []const u8) u16 {
var soma: u32 = 0;
var i: usize = 0;
while (i + 1 < data.len) : (i += 2) {
soma += @as(u32, data[i]) << 8 | @as(u32, data[i + 1]);
}
if (i < data.len) {
soma += @as(u32, data[i]) << 8;
}
while (soma >> 16 != 0) {
soma = (soma & 0xFFFF) + (soma >> 16);
}
return @truncate(~soma);
}
pub fn main() !void {
// Requer CAP_NET_RAW ou root
const sock = try posix.socket(
posix.AF.INET,
posix.SOCK.RAW,
posix.IPPROTO.ICMP,
);
defer posix.close(sock);
// Construir pacote ICMP Echo Request
var pacote: [64]u8 = std.mem.zeroes([64]u8);
const header: *IcmpHeader = @ptrCast(@alignCast(&pacote));
header.tipo = 8; // Echo Request
header.codigo = 0;
header.identificador = @byteSwap(@as(u16, @truncate(std.os.linux.getpid())));
header.sequencia = @byteSwap(@as(u16, 1));
// Preencher dados
for (pacote[@sizeOf(IcmpHeader)..]) |*byte| {
byte.* = 0x42;
}
// Calcular checksum
header.checksum = 0;
header.checksum = calcularChecksum(&pacote);
// Enviar para 8.8.8.8 (Google DNS)
const destino = std.net.Address.initIp4(.{ 8, 8, 8, 8 }, 0);
_ = try posix.sendto(
sock,
&pacote,
0,
&destino.any,
destino.getOsSockLen(),
);
std.debug.print("Ping enviado para 8.8.8.8\n", .{});
// Aguardar resposta
var resposta: [1024]u8 = undefined;
const n = try posix.read(sock, &resposta);
if (n > 20) { // Cabecalho IP tem pelo menos 20 bytes
const icmp_resp: *const IcmpHeader = @ptrCast(@alignCast(&resposta[20]));
if (icmp_resp.tipo == 0) {
std.debug.print("Pong recebido! ({d} bytes)\n", .{n});
}
}
}
Resolucao DNS Manual
Entender como DNS funciona no nivel do socket:
const std = @import("std");
const net = std.net;
pub fn resolverDNS(allocator: std.mem.Allocator, hostname: []const u8) ![]net.Address {
var enderecos = std.ArrayList(net.Address).init(allocator);
defer enderecos.deinit();
// Usar a resolucao DNS da standard library
const lista = try net.getAddressList(allocator, hostname, 80);
defer lista.deinit();
for (lista.addrs) |addr| {
try enderecos.append(addr);
}
return try enderecos.toOwnedSlice();
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const hosts = [_][]const u8{
"ziglang.org",
"github.com",
"google.com",
};
for (hosts) |host| {
std.debug.print("\nResolvendo {s}:\n", .{host});
const enderecos = resolverDNS(allocator, host) catch |err| {
std.debug.print(" Erro: {}\n", .{err});
continue;
};
defer allocator.free(enderecos);
for (enderecos) |addr| {
std.debug.print(" -> {}\n", .{addr});
}
}
}
Opcoes de Socket Avancadas
const std = @import("std");
const posix = std.posix;
pub fn configurarSocket(sock: posix.socket_t) !void {
// Reutilizar endereco (evita "address already in use")
try posix.setsockopt(sock, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1)));
// Timeout de leitura (5 segundos)
const timeout = posix.timeval{
.tv_sec = 5,
.tv_usec = 0,
};
try posix.setsockopt(sock, posix.SOL.SOCKET, posix.SO.RCVTIMEO, std.mem.asBytes(&timeout));
// TCP Keep-Alive
try posix.setsockopt(sock, posix.SOL.SOCKET, posix.SO.KEEPALIVE, &std.mem.toBytes(@as(c_int, 1)));
// Desabilitar Nagle algorithm (util para baixa latencia)
try posix.setsockopt(sock, posix.IPPROTO.TCP, std.os.linux.TCP.NODELAY, &std.mem.toBytes(@as(c_int, 1)));
}
Comparacao: TCP vs UDP vs Raw
| Aspecto | TCP | UDP | Raw |
|---|---|---|---|
| Confiabilidade | Garantida | Sem garantia | Depende de voce |
| Ordem | Mantida | Nao garantida | Depende de voce |
| Overhead | Alto (handshake, ACK) | Baixo | Minimo |
| Uso tipico | HTTP, SSH, DB | DNS, games, VoIP | Ping, sniffing |
| Privilegios | Nenhum especial | Nenhum especial | Root/CAP_NET_RAW |
Exercicios
Chat server: Implemente um servidor de chat TCP que aceite multiplos clientes e retransmita mensagens entre todos os conectados.
Port scanner: Crie um scanner que verifique quais portas TCP estao abertas em um endereco IP.
DNS lookup: Implemente um cliente DNS simples que envie queries UDP para um servidor DNS e parse as respostas.
Proximo Artigo
No artigo final da serie, exploramos io_uring e I/O assincrono, a interface mais moderna e performatica para I/O no Linux.
Conteudo Relacionado
- Artigo anterior: Processos e Signals
- Servidor HTTP em Zig — Aplicacao pratica completa
- Zig HTTP Client — Cliente HTTP
- Web Development com Zig — Serie completa
Duvidas sobre networking em Zig? Participe da comunidade Zig Brasil e troque experiencias!