Módulo std::net em Rust

Referência completa do módulo std::net em Rust: TcpListener, TcpStream, UdpSocket, SocketAddr, IpAddr e exemplos de servidor/cliente TCP.

Módulo std::net em Rust

O módulo std::net fornece os tipos fundamentais para programação de rede em Rust: TcpListener e TcpStream para conexões TCP, UdpSocket para datagrams UDP, e tipos auxiliares como SocketAddr, IpAddr, Ipv4Addr e Ipv6Addr. Todas as operações são síncronas (bloqueantes) — para I/O assíncrono, use a crate tokio ou async-std.

Visão geral e tipos-chave

TCP

  • TcpListener — servidor que aceita conexões TCP em um endereço/porta. Análogo ao bind() + listen() do BSD sockets.
  • TcpStream — conexão TCP bidirecional. Implementa Read e Write, permitindo usar todos os padrões de I/O do Rust.

UDP

  • UdpSocket — socket para envio e recebimento de datagrams UDP. Sem conexão, sem garantia de entrega ou ordem.

Endereçamento

  • SocketAddr — endereço IP + porta (v4 ou v6)
  • IpAddr — endereço IP (v4 ou v6)
  • Ipv4Addr — endereço IPv4 (ex: 127.0.0.1)
  • Ipv6Addr — endereço IPv6 (ex: ::1)
  • ToSocketAddrs — trait para resolução de nomes (aceita strings como "localhost:8080")

Padrões comuns com código

Servidor TCP básico

use std::io::{self, BufRead, BufReader, Write};
use std::net::TcpListener;

fn main() -> io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:8080")?;
    println!("Servidor ouvindo em 127.0.0.1:8080");

    for stream in listener.incoming() {
        let mut stream = stream?;
        let endereco = stream.peer_addr()?;
        println!("Conexão de: {}", endereco);

        let leitor = BufReader::new(stream.try_clone()?);
        for linha in leitor.lines() {
            let linha = linha?;
            if linha.is_empty() {
                break;
            }
            println!("  Recebido: {}", linha);
        }

        // Resposta HTTP simples
        let corpo = "Olá do servidor Rust!";
        let resposta = format!(
            "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n{}",
            corpo.len(),
            corpo
        );
        stream.write_all(resposta.as_bytes())?;
        stream.flush()?;
    }

    Ok(())
}

Cliente TCP básico

use std::io::{self, BufRead, BufReader, Write};
use std::net::TcpStream;

fn main() -> io::Result<()> {
    let mut stream = TcpStream::connect("127.0.0.1:8080")?;
    println!("Conectado a {}", stream.peer_addr()?);

    // Enviar requisição HTTP
    stream.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")?;
    stream.flush()?;

    // Ler resposta
    let leitor = BufReader::new(&stream);
    for linha in leitor.lines() {
        let linha = linha?;
        println!("{}", linha);
        if linha.is_empty() {
            break; // fim dos cabeçalhos
        }
    }

    Ok(())
}

Endereços IP e resolução de nomes

use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs};

fn main() {
    // Criar endereços IP
    let ipv4 = Ipv4Addr::new(192, 168, 1, 1);
    let ipv6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); // ::1
    let ip: IpAddr = IpAddr::V4(ipv4);

    println!("IPv4: {}", ipv4);
    println!("IPv6: {}", ipv6);
    println!("Loopback v4: {}", Ipv4Addr::LOCALHOST);     // 127.0.0.1
    println!("Qualquer v4: {}", Ipv4Addr::UNSPECIFIED);    // 0.0.0.0
    println!("É loopback? {}", ipv4.is_loopback());        // false
    println!("É privado? {}", ipv4.is_private());           // true

    // SocketAddr = IP + porta
    let addr = SocketAddr::new(ip, 8080);
    println!("Socket: {}", addr);

    // Parse de string
    let parsed: SocketAddr = "127.0.0.1:3000".parse().unwrap();
    println!("Parsed: {}", parsed);

    // Resolução de nomes (ToSocketAddrs)
    if let Ok(enderecos) = "localhost:80".to_socket_addrs() {
        for addr in enderecos {
            println!("Resolvido: {}", addr);
        }
    }
}

Tabela de métodos e funções

TcpListener

MétodoRetornoDescrição
TcpListener::bind(addr)io::Result<TcpListener>Cria listener vinculado ao endereço
accept()io::Result<(TcpStream, SocketAddr)>Aceita uma conexão (bloqueante)
incoming()IncomingIterador infinito de conexões
local_addr()io::Result<SocketAddr>Endereço local vinculado
set_nonblocking(bool)io::Result<()>Modo não-bloqueante
set_ttl(ttl)io::Result<()>Define TTL dos pacotes
try_clone()io::Result<TcpListener>Duplica o listener

TcpStream

MétodoRetornoDescrição
TcpStream::connect(addr)io::Result<TcpStream>Conecta a um servidor
TcpStream::connect_timeout(addr, dur)io::Result<TcpStream>Conecta com timeout
peer_addr()io::Result<SocketAddr>Endereço remoto
local_addr()io::Result<SocketAddr>Endereço local
shutdown(how)io::Result<()>Encerra leitura, escrita ou ambos
set_read_timeout(dur)io::Result<()>Timeout de leitura
set_write_timeout(dur)io::Result<()>Timeout de escrita
set_nonblocking(bool)io::Result<()>Modo não-bloqueante
set_nodelay(bool)io::Result<()>Desabilita Nagle (TCP_NODELAY)
try_clone()io::Result<TcpStream>Duplica o stream

UdpSocket

MétodoRetornoDescrição
UdpSocket::bind(addr)io::Result<UdpSocket>Cria socket vinculado
send_to(buf, addr)io::Result<usize>Envia datagram para endereço
recv_from(buf)io::Result<(usize, SocketAddr)>Recebe datagram e origem
connect(addr)io::Result<()>Define destino padrão
send(buf)io::Result<usize>Envia para destino conectado
recv(buf)io::Result<usize>Recebe do destino conectado
set_broadcast(bool)io::Result<()>Habilita broadcast
set_read_timeout(dur)io::Result<()>Timeout de leitura

Exemplos práticos

Exemplo 1: Servidor TCP multi-thread com protocolo de chat

use std::io::{self, BufRead, BufReader, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::{Arc, Mutex};
use std::thread;

fn tratar_cliente(
    stream: TcpStream,
    clientes: Arc<Mutex<Vec<TcpStream>>>,
) -> io::Result<()> {
    let endereco = stream.peer_addr()?;
    println!("[+] {} conectou", endereco);

    let leitor = BufReader::new(stream.try_clone()?);

    // Adicionar à lista de clientes
    {
        let mut lista = clientes.lock().unwrap();
        lista.push(stream.try_clone()?);
    }

    for linha in leitor.lines() {
        let linha = linha?;
        if linha.trim().is_empty() {
            continue;
        }

        let mensagem = format!("[{}] {}\n", endereco, linha);
        println!("{}", mensagem.trim());

        // Broadcast para todos os clientes
        let mut lista = clientes.lock().unwrap();
        lista.retain(|cliente| {
            let mut c = cliente.try_clone().unwrap();
            c.write_all(mensagem.as_bytes()).is_ok()
        });
    }

    println!("[-] {} desconectou", endereco);
    Ok(())
}

fn main() -> io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:9999")?;
    println!("Chat servidor em 127.0.0.1:9999");

    let clientes: Arc<Mutex<Vec<TcpStream>>> = Arc::new(Mutex::new(Vec::new()));

    for stream in listener.incoming() {
        let stream = stream?;
        let clientes = Arc::clone(&clientes);

        thread::spawn(move || {
            if let Err(e) = tratar_cliente(stream, clientes) {
                eprintln!("Erro no cliente: {}", e);
            }
        });
    }

    Ok(())
}

Exemplo 2: Cliente TCP com timeout e retry

use std::io::{self, BufRead, BufReader, Write};
use std::net::{SocketAddr, TcpStream};
use std::time::Duration;

fn conectar_com_retry(
    endereco: &str,
    tentativas: usize,
    timeout: Duration,
) -> io::Result<TcpStream> {
    let addr: SocketAddr = endereco.parse().map_err(|e| {
        io::Error::new(io::ErrorKind::InvalidInput, format!("Endereço inválido: {}", e))
    })?;

    for i in 1..=tentativas {
        eprintln!("Tentativa {}/{} para {}...", i, tentativas, endereco);

        match TcpStream::connect_timeout(&addr, timeout) {
            Ok(stream) => {
                stream.set_read_timeout(Some(Duration::from_secs(30)))?;
                stream.set_write_timeout(Some(Duration::from_secs(10)))?;
                stream.set_nodelay(true)?;
                eprintln!("Conectado!");
                return Ok(stream);
            }
            Err(e) if i < tentativas => {
                eprintln!("Falha: {}. Tentando novamente em 2s...", e);
                std::thread::sleep(Duration::from_secs(2));
            }
            Err(e) => return Err(e),
        }
    }

    unreachable!()
}

fn enviar_requisicao(stream: &mut TcpStream, caminho: &str, host: &str) -> io::Result<String> {
    let requisicao = format!(
        "GET {} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n",
        caminho, host
    );
    stream.write_all(requisicao.as_bytes())?;
    stream.flush()?;

    let leitor = BufReader::new(stream);
    let mut resposta = String::new();
    for linha in leitor.lines() {
        resposta.push_str(&linha?);
        resposta.push('\n');
    }

    Ok(resposta)
}

fn main() -> io::Result<()> {
    let mut stream = conectar_com_retry(
        "93.184.216.34:80", // example.com
        3,
        Duration::from_secs(5),
    )?;

    let resposta = enviar_requisicao(&mut stream, "/", "example.com")?;
    println!("Resposta ({} bytes):\n{}", resposta.len(), &resposta[..500.min(resposta.len())]);

    Ok(())
}

Exemplo 3: Servidor e cliente UDP

use std::net::UdpSocket;
use std::io;

fn servidor_udp() -> io::Result<()> {
    let socket = UdpSocket::bind("127.0.0.1:5000")?;
    println!("Servidor UDP em 127.0.0.1:5000");

    let mut buf = [0u8; 1024];
    loop {
        let (n, origem) = socket.recv_from(&mut buf)?;
        let mensagem = String::from_utf8_lossy(&buf[..n]);
        println!("De {}: {}", origem, mensagem);

        // Eco — responder com a mensagem em maiúsculas
        let resposta = mensagem.to_uppercase();
        socket.send_to(resposta.as_bytes(), origem)?;
    }
}

fn cliente_udp() -> io::Result<()> {
    let socket = UdpSocket::bind("0.0.0.0:0")?; // porta aleatória
    let servidor = "127.0.0.1:5000";

    let mensagens = vec!["olá servidor", "rust é incrível", "teste udp"];

    for msg in mensagens {
        socket.send_to(msg.as_bytes(), servidor)?;

        let mut buf = [0u8; 1024];
        socket.set_read_timeout(Some(std::time::Duration::from_secs(5)))?;
        let (n, _) = socket.recv_from(&mut buf)?;
        let resposta = String::from_utf8_lossy(&buf[..n]);
        println!("Enviado: '{}' -> Resposta: '{}'", msg, resposta);
    }

    Ok(())
}

fn main() -> io::Result<()> {
    let args: Vec<String> = std::env::args().collect();
    match args.get(1).map(|s| s.as_str()) {
        Some("server") => servidor_udp(),
        Some("client") => cliente_udp(),
        _ => {
            eprintln!("Uso: {} <server|client>", args[0]);
            Ok(())
        }
    }
}

Exemplo 4: Scanner de portas simples

use std::net::{SocketAddr, TcpStream};
use std::time::Duration;
use std::sync::mpsc;
use std::thread;

fn scan_porta(addr: SocketAddr, timeout: Duration) -> bool {
    TcpStream::connect_timeout(&addr, timeout).is_ok()
}

fn scan_host(host: &str, porta_inicio: u16, porta_fim: u16) -> Vec<u16> {
    let (tx, rx) = mpsc::channel();
    let timeout = Duration::from_millis(200);

    let mut handles = Vec::new();

    for porta in porta_inicio..=porta_fim {
        let tx = tx.clone();
        let host = host.to_string();

        let handle = thread::spawn(move || {
            let addr: SocketAddr = format!("{}:{}", host, porta).parse().unwrap();
            if scan_porta(addr, timeout) {
                tx.send(porta).unwrap();
            }
        });
        handles.push(handle);
    }

    drop(tx); // fechar sender original

    // Coletar resultados
    let mut abertas: Vec<u16> = rx.iter().collect();
    abertas.sort();

    for handle in handles {
        let _ = handle.join();
    }

    abertas
}

fn main() {
    let host = "127.0.0.1";
    println!("Escaneando {}:1-1024...", host);

    let abertas = scan_host(host, 1, 1024);

    if abertas.is_empty() {
        println!("Nenhuma porta aberta encontrada");
    } else {
        println!("Portas abertas:");
        for porta in &abertas {
            println!("  {} TCP", porta);
        }
    }
}

Exemplo 5: Servidor HTTP mínimo com rotas

use std::collections::HashMap;
use std::io::{self, BufRead, BufReader, Write};
use std::net::TcpListener;

fn parse_requisicao(leitor: &mut BufReader<&std::net::TcpStream>) -> io::Result<(String, String)> {
    let mut primeira_linha = String::new();
    leitor.read_line(&mut primeira_linha)?;

    let partes: Vec<&str> = primeira_linha.split_whitespace().collect();
    if partes.len() < 2 {
        return Err(io::Error::new(io::ErrorKind::InvalidData, "Requisição inválida"));
    }

    let metodo = partes[0].to_string();
    let caminho = partes[1].to_string();

    // Consumir cabeçalhos
    let mut linha = String::new();
    loop {
        linha.clear();
        leitor.read_line(&mut linha)?;
        if linha.trim().is_empty() {
            break;
        }
    }

    Ok((metodo, caminho))
}

fn resposta_http(status: u16, corpo: &str) -> String {
    let texto_status = match status {
        200 => "OK",
        404 => "Not Found",
        405 => "Method Not Allowed",
        _ => "Internal Server Error",
    };

    format!(
        "HTTP/1.1 {} {}\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
        status,
        texto_status,
        corpo.len(),
        corpo
    )
}

fn main() -> io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:3000")?;
    println!("Servidor HTTP em http://127.0.0.1:3000");

    for stream in listener.incoming() {
        let stream = stream?;
        let mut leitor = BufReader::new(&stream);

        let (metodo, caminho) = match parse_requisicao(&mut leitor) {
            Ok(r) => r,
            Err(_) => continue,
        };

        println!("{} {}", metodo, caminho);

        let resposta = if metodo != "GET" {
            resposta_http(405, "<h1>Método não permitido</h1>")
        } else {
            match caminho.as_str() {
                "/" => resposta_http(200, "<h1>Bem-vindo ao servidor Rust!</h1><p>Feito com std::net</p>"),
                "/sobre" => resposta_http(200, "<h1>Sobre</h1><p>Servidor HTTP mínimo em Rust puro.</p>"),
                "/api/status" => resposta_http(200, "{\"status\": \"ok\", \"versao\": \"1.0\"}"),
                _ => resposta_http(404, "<h1>Página não encontrada</h1>"),
            }
        };

        let mut escritor = stream;
        escritor.write_all(resposta.as_bytes())?;
        escritor.flush()?;
    }

    Ok(())
}

Padrões de tratamento de erro para I/O

Tratando erros de conexão

use std::io;
use std::net::TcpStream;

fn conectar_diagnostico(endereco: &str) -> io::Result<TcpStream> {
    TcpStream::connect(endereco).map_err(|e| {
        match e.kind() {
            io::ErrorKind::ConnectionRefused => {
                eprintln!("Conexão recusada: servidor pode não estar em execução em {}", endereco);
            }
            io::ErrorKind::TimedOut => {
                eprintln!("Timeout: servidor {} não respondeu a tempo", endereco);
            }
            io::ErrorKind::AddrNotAvailable => {
                eprintln!("Endereço não disponível: {}", endereco);
            }
            _ => {
                eprintln!("Erro de conexão para {}: {}", endereco, e);
            }
        }
        e
    })
}

Dicas de desempenho

  • Use set_nodelay(true) para aplicações de baixa latência (desabilita o algoritmo de Nagle, que agrupa pacotes pequenos).
  • Prefira BufReader/BufWriter ao redor de TcpStream — reduz syscalls para protocolos textuais.
  • Use threads ou async para múltiplas conexões simultâneas — o modelo síncrono bloqueia uma thread por conexão.
  • Defina timeouts com set_read_timeout/set_write_timeout para evitar que conexões lentas travem o servidor.

Veja também