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")
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étodo | Retorno | Descrição |
|---|
TcpListener::bind(addr) | io::Result<TcpListener> | Cria listener vinculado ao endereço |
accept() | io::Result<(TcpStream, SocketAddr)> | Aceita uma conexão (bloqueante) |
incoming() | Incoming | Iterador 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étodo | Retorno | Descriçã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étodo | Retorno | Descriçã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
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(())
}
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);
}
}
}
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