Introdução
Um servidor TCP é um programa que escuta em uma porta específica, aceita conexões de clientes e processa suas requisições. Servidores TCP são a base de muitos protocolos de aplicação como HTTP, FTP e SMTP.
Nesta receita, você aprenderá a construir servidores TCP em Zig, desde um servidor simples de conexão única até um servidor que gerencia múltiplos clientes simultaneamente.
Pré-requisitos
- Zig instalado (versão 0.13+). Veja o guia de instalação
- Conhecimento básico de Zig. Consulte a introdução ao Zig
Servidor TCP Básico
Este servidor aceita uma conexão por vez, lê a mensagem do cliente e envia uma resposta:
const std = @import("std");
const net = std.net;
pub fn main() !void {
// Criar o servidor escutando em 0.0.0.0:8080
const address = net.Address.initIp4(.{ 0, 0, 0, 0 }, 8080);
var server = try address.listen(.{
.reuse_address = true,
});
defer server.deinit();
std.debug.print("Servidor escutando na porta 8080...\n", .{});
// Loop principal: aceitar conexões
while (true) {
const connection = try server.accept();
defer connection.stream.close();
std.debug.print("Cliente conectado: {}\n", .{connection.address});
// Ler dados do cliente
var buffer: [1024]u8 = undefined;
const bytes_read = try connection.stream.read(&buffer);
if (bytes_read > 0) {
const message = buffer[0..bytes_read];
std.debug.print("Recebido: {s}\n", .{message});
// Enviar resposta
const response = "Mensagem recebida com sucesso!\n";
_ = try connection.stream.write(response);
}
}
}
Como testar
Em um terminal, inicie o servidor:
zig build-exe server.zig && ./server
Em outro terminal, conecte-se com netcat:
echo "Olá, servidor!" | nc localhost 8080
Saída esperada
No servidor:
Servidor escutando na porta 8080...
Cliente conectado: 127.0.0.1:54321
Recebido: Olá, servidor!
No cliente:
Mensagem recebida com sucesso!
Servidor Echo (Eco)
Um servidor echo repete de volta tudo que o cliente envia. É útil para testes de rede:
const std = @import("std");
const net = std.net;
pub fn main() !void {
const address = net.Address.initIp4(.{ 0, 0, 0, 0 }, 8080);
var server = try address.listen(.{
.reuse_address = true,
});
defer server.deinit();
std.debug.print("Servidor echo na porta 8080...\n", .{});
while (true) {
const connection = try server.accept();
defer connection.stream.close();
// Eco: enviar de volta tudo que receber
var buffer: [4096]u8 = undefined;
while (true) {
const bytes_read = connection.stream.read(&buffer) catch break;
if (bytes_read == 0) break;
// Escrever de volta exatamente o que foi recebido
_ = connection.stream.write(buffer[0..bytes_read]) catch break;
}
std.debug.print("Cliente desconectado.\n", .{});
}
}
Servidor Multi-cliente com Threads
Para atender múltiplos clientes simultaneamente, use threads:
const std = @import("std");
const net = std.net;
fn handleClient(connection: net.Server.Connection) void {
defer connection.stream.close();
std.debug.print("Novo cliente: {}\n", .{connection.address});
var buffer: [4096]u8 = undefined;
while (true) {
const bytes_read = connection.stream.read(&buffer) catch |err| {
std.debug.print("Erro de leitura: {}\n", .{err});
break;
};
if (bytes_read == 0) break;
const message = buffer[0..bytes_read];
std.debug.print("[{}] {s}\n", .{ connection.address, message });
// Responder ao cliente
const response = "OK\n";
_ = connection.stream.write(response) catch break;
}
std.debug.print("Cliente desconectado: {}\n", .{connection.address});
}
pub fn main() !void {
const address = net.Address.initIp4(.{ 0, 0, 0, 0 }, 8080);
var server = try address.listen(.{
.reuse_address = true,
.kernel_backlog = 128,
});
defer server.deinit();
std.debug.print("Servidor multi-thread na porta 8080...\n", .{});
while (true) {
const connection = try server.accept();
// Criar uma thread para cada conexão
const thread = std.Thread.spawn(.{}, handleClient, .{connection}) catch |err| {
std.debug.print("Erro ao criar thread: {}\n", .{err});
connection.stream.close();
continue;
};
thread.detach();
}
}
Servidor com Protocolo Customizado
Este exemplo implementa um protocolo simples com comprimento prefixado:
const std = @import("std");
const net = std.net;
const Protocol = struct {
/// Enviar mensagem com prefixo de comprimento (4 bytes big-endian)
pub fn sendMessage(stream: net.Stream, data: []const u8) !void {
// Enviar comprimento como 4 bytes big-endian
const len: u32 = @intCast(data.len);
const len_bytes = std.mem.toBytes(std.mem.nativeToBig(u32, len));
_ = try stream.write(&len_bytes);
_ = try stream.write(data);
}
/// Receber mensagem com prefixo de comprimento
pub fn recvMessage(stream: net.Stream, buffer: []u8) !?[]const u8 {
// Ler 4 bytes do comprimento
var len_bytes: [4]u8 = undefined;
const header_read = try stream.read(&len_bytes);
if (header_read == 0) return null;
if (header_read != 4) return error.IncompleteHeader;
const len = std.mem.bigToNative(u32, std.mem.bytesToValue(u32, &len_bytes));
if (len > buffer.len) return error.MessageTooLarge;
// Ler o corpo da mensagem
var total_read: usize = 0;
while (total_read < len) {
const n = try stream.read(buffer[total_read..len]);
if (n == 0) return error.ConnectionClosed;
total_read += n;
}
return buffer[0..len];
}
};
pub fn main() !void {
const address = net.Address.initIp4(.{ 0, 0, 0, 0 }, 9000);
var server = try address.listen(.{ .reuse_address = true });
defer server.deinit();
std.debug.print("Servidor de protocolo customizado na porta 9000...\n", .{});
while (true) {
const conn = try server.accept();
defer conn.stream.close();
var buffer: [65536]u8 = undefined;
while (true) {
const msg = Protocol.recvMessage(conn.stream, &buffer) catch break;
if (msg == null) break;
std.debug.print("Mensagem: {s}\n", .{msg.?});
// Responder com protocolo
Protocol.sendMessage(conn.stream, "Recebido!") catch break;
}
}
}
Dicas e Boas Práticas
Use
reuse_address: A opção.reuse_address = truepermite reiniciar o servidor rapidamente sem esperar o timeout do SO.Defina backlog adequado: O
kernel_backlogdefine quantas conexões pendentes o kernel mantém na fila.Feche conexões com defer: Use
defer connection.stream.close()para garantir limpeza correta.Limite o número de threads: Em produção, considere usar um pool de threads em vez de criar uma thread por conexão.
Trate erros de rede graciosamente: Clientes podem desconectar a qualquer momento. Use o tratamento de erros para lidar com isso.
Receitas Relacionadas
- Como criar um cliente TCP em Zig - Para testar seu servidor
- Como usar sockets UDP em Zig - Servidor UDP alternativo
- Como criar threads em Zig - Mais sobre threads
- Como usar thread pool em Zig - Pool de threads para servidores