std.net.Stream em Zig — Referência e Exemplos

std.net.Stream — Conexão TCP Bidirecional

O std.net.Stream representa uma conexão TCP estabelecida e bidirecional. Ele encapsula um descritor de socket do sistema operacional e fornece métodos para leitura e escrita de dados, além de implementar as interfaces Reader e Writer do std.io, permitindo usar todas as funções de I/O genéricas do Zig.

Visão Geral

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

Estrutura

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

    pub fn read(self: Stream, buffer: []u8) ReadError!usize
    pub fn readv(self: Stream, iovecs: []std.posix.iovec) ReadError!usize
    pub fn write(self: Stream, buffer: []const u8) WriteError!usize
    pub fn writev(self: Stream, iovecs: []std.posix.iovec_const) WriteError!usize
    pub fn close(self: Stream) void

    pub fn reader(self: Stream) std.io.GenericReader(...)
    pub fn writer(self: Stream) std.io.GenericWriter(...)
};

O Stream é um tipo leve (apenas um handle) que pode ser copiado livremente. Chamar close() em qualquer cópia fecha a conexão para todas.

Funções Principais

Leitura

// Lê bytes no buffer, retorna quantidade lida (0 = fim)
pub fn read(self: Stream, buffer: []u8) ReadError!usize

// Lê usando scatter (múltiplos buffers)
pub fn readv(self: Stream, iovecs: []std.posix.iovec) ReadError!usize

// Retorna Reader compatível com std.io
pub fn reader(self: Stream) Reader

Escrita

// Escreve bytes, retorna quantidade escrita
pub fn write(self: Stream, buffer: []const u8) WriteError!usize

// Escreve usando gather (múltiplos buffers)
pub fn writev(self: Stream, iovecs: []std.posix.iovec_const) WriteError!usize

// Retorna Writer compatível com std.io
pub fn writer(self: Stream) Writer

Controle

// Fecha o socket
pub fn close(self: Stream) void

// Obtém informações do socket
pub fn getLocalAddress(self: Stream) !net.Address
pub fn getRemoteAddress(self: Stream) !net.Address

Exemplo 1: Cliente que Envia e Recebe Dados

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 ao servidor
    const stream = net.tcpConnectToHost(allocator, "example.com", 80) catch |err| {
        try stdout.print("Falha na conexão: {}\n", .{err});
        return;
    };
    defer stream.close();

    // Usa a interface Writer para enviar HTTP
    const writer = stream.writer();
    try writer.writeAll("GET / HTTP/1.1\r\n");
    try writer.writeAll("Host: example.com\r\n");
    try writer.writeAll("Connection: close\r\n");
    try writer.writeAll("\r\n");

    // Usa a interface Reader para ler a resposta
    const reader = stream.reader();
    var buf: [4096]u8 = undefined;

    // Lê primeira linha (status HTTP)
    if (try reader.readUntilDelimiterOrEof(&buf, '\n')) |linha| {
        try stdout.print("Status: {s}\n", .{std.mem.trim(u8, linha, "\r")});
    }

    // Conta bytes totais da resposta
    var total_bytes: usize = 0;
    while (true) {
        const n = try reader.read(&buf);
        if (n == 0) break;
        total_bytes += n;
    }

    try stdout.print("Total recebido: {d} bytes\n", .{total_bytes});
}

Exemplo 2: Protocolo de Mensagens com Tamanho Prefixado

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

const Protocolo = struct {
    stream: net.Stream,

    // Envia mensagem com prefixo de tamanho (4 bytes, big-endian)
    pub fn enviarMensagem(self: Protocolo, dados: []const u8) !void {
        const writer = self.stream.writer();
        const tamanho: u32 = @intCast(dados.len);

        // Escreve o tamanho primeiro
        try writer.writeInt(u32, tamanho, .big);
        // Depois os dados
        try writer.writeAll(dados);
    }

    // Recebe mensagem com prefixo de tamanho
    pub fn receberMensagem(self: Protocolo, buf: []u8) !?[]u8 {
        const reader = self.stream.reader();

        // Lê o tamanho (4 bytes)
        const tamanho = reader.readInt(u32, .big) catch |err| {
            if (err == error.EndOfStream) return null;
            return err;
        };

        if (tamanho > buf.len) return error.BufferMuitoPequeno;

        // Lê exatamente 'tamanho' bytes
        const slice = buf[0..tamanho];
        try reader.readNoEof(slice);
        return slice;
    }
};

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

    // Demonstra o protocolo com um fixedBufferStream (simulação)
    var buf_dados: [1024]u8 = undefined;
    var stream_buf = std.io.fixedBufferStream(&buf_dados);

    // Simula escrita de mensagem
    const writer = stream_buf.writer();
    const mensagem = "Olá, protocolo!";
    const tamanho: u32 = @intCast(mensagem.len);
    try writer.writeInt(u32, tamanho, .big);
    try writer.writeAll(mensagem);

    // Simula leitura
    stream_buf.reset();
    const reader = stream_buf.reader();
    const tam_lido = try reader.readInt(u32, .big);
    var buf_recv: [256]u8 = undefined;
    try reader.readNoEof(buf_recv[0..tam_lido]);

    try stdout.print("Mensagem recebida ({d} bytes): {s}\n", .{
        tam_lido,
        buf_recv[0..tam_lido],
    });
}

Exemplo 3: Proxy TCP Simples

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

fn encaminhar(origem: net.Stream, destino: net.Stream) void {
    var buf: [4096]u8 = undefined;
    while (true) {
        const n = origem.read(&buf) catch break;
        if (n == 0) break;
        destino.writer().writeAll(buf[0..n]) catch break;
    }
}

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

    const porta_local: u16 = 9000;
    const host_destino = "example.com";
    const porta_destino: u16 = 80;

    const addr = try net.Address.parseIp4("127.0.0.1", porta_local);
    var server = try addr.listen(.{ .reuse_address = true });
    defer server.deinit();

    std.debug.print("Proxy escutando em :{d} -> {s}:{d}\n", .{
        porta_local, host_destino, porta_destino,
    });

    while (true) {
        const conn = server.accept() catch continue;
        std.debug.print("Nova conexão de {}\n", .{conn.address});

        // Conecta ao destino
        const destino = net.tcpConnectToHost(
            allocator,
            host_destino,
            porta_destino,
        ) catch |err| {
            std.debug.print("Erro ao conectar ao destino: {}\n", .{err});
            conn.stream.close();
            continue;
        };

        // Em produção, use threads separadas para cada direção
        encaminhar(conn.stream, destino);

        destino.close();
        conn.stream.close();
    }
}

Módulos Relacionados

Tutoriais e Receitas Relacionados

Continue aprendendo Zig

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