Networking with the Standard Library
TCP Listeners
A TcpListener (std::net::TcpListener) is a server-side TCP socket bound to a local IP address and port that listens for incoming client connection requests. Each accepted handshake produces a dedicated TcpStream for communicating with that client while the TcpListener continues listening for new connections.
+-----------------------+
Client 1 -- SYN/ACK ----> | TcpListener | ---> TcpStream 1 (Client 1)
Client 2 -- SYN/ACK ----> | bound to 0.0.0.0:80 | ---> TcpStream 2 (Client 2)
Client 3 -- SYN/ACK ----> | .accept() / .incoming() TcpStream 3 (Client 3)
+-----------------------+
Binding a TcpListener
TcpListener::bind<A: ToSocketAddrs>(addr: A) creates a socket, binds it to the specified address, and puts it into the listening state:
- Binding to
"127.0.0.1:8080"accepts connections only from the local machine (loopback). - Binding to
"0.0.0.0:8080"(or"[::]:8080") accepts connections arriving on any network interface of the host. - Binding to port
0("127.0.0.1:0") instructs the OS kernel to select an available ephemeral port automatically.
.accept() vs .incoming()
TcpListener provides two ways to receive incoming connections:
| Method | Signature | Returns | Best Used When |
|---|---|---|---|
.accept() |
accept(&self) -> io::Result<(TcpStream, SocketAddr)> |
Both the connected TcpStream and the client's remote SocketAddr. |
You immediately need the client's IP and port for logging, rate-limiting, or access control. |
.incoming() |
incoming(&self) -> Incoming<'_> |
An infinite iterator yielding io::Result<TcpStream>. |
You want a concise for stream in listener.incoming() server loop. |
Note that iterating over listener.incoming() never returns None; each item is an io::Result<TcpStream> representing a single connection attempt (which may fail if the client resets the handshake or the process runs out of file descriptors).
use std::io::{self, Read, Write};
use std::net::{TcpListener, TcpStream};
use std::thread;
fn main() -> io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:0")?;
let server_addr = listener.local_addr()?;
// Spawn 3 client connections on a background thread
let client_worker = thread::spawn(move || -> io::Result<()> {
for id in 1..=3 {
let mut stream = TcpStream::connect(server_addr)?;
write!(stream, "Hello from client #{id}")?;
}
Ok(())
});
// Accept exactly 3 connections using `.incoming().take(3)`
for (idx, stream_result) in listener.incoming().take(3).enumerate() {
let mut stream = stream_result?;
let mut msg = String::new();
stream.read_to_string(&mut msg)?;
println!("Accepted [{}]: {}", idx + 1, msg);
}
client_worker.join().expect("client thread panicked")?;
Ok(())
}
Accepted [1]: Hello from client #1
Accepted [2]: Hello from client #2
Accepted [3]: Hello from client #3
Setting Time-To-Live (TTL) and Non-Blocking Mode
listener.set_ttl(ttl: u32)sets the IPTTLfield on packets sent from the socket.listener.set_nonblocking(true)switches the listener into non-blocking mode solistener.accept()returnsErr(ErrorKind::WouldBlock)immediately when no pending client connection is waiting in the kernel accept queue.