TCP Streams
A TcpStream (std::net::TcpStream) represents an active, bidirectional, reliable byte stream connection between a local socket and a remote peer over TCP. It implements both std::io::Read and std::io::Write, allowing applications to read and write network data using the exact same traits and buffered wrappers (BufReader, BufWriter) used for files.
Local Endpoint (local_addr) Remote Peer (peer_addr)
+-------------------------+ +-------------------------+
| TcpStream | === Outbound ===> | TcpStream |
| impl Read + Write | <=== Inbound ==== | impl Read + Write |
+-------------------------+ +-------------------------+
Establishing and Inspecting a TcpStream
A TcpStream is created either on the client side via TcpStream::connect (or TcpStream::connect_timeout) or on the server side via TcpListener::accept.
| Method | Signature | Description |
|---|---|---|
TcpStream::connect(addr) |
connect<A: ToSocketAddrs>(addr: A) -> io::Result<TcpStream> |
Initiates a TCP handshake with addr (trying each resolved address until one succeeds). |
TcpStream::connect_timeout(&addr, dur) |
connect_timeout(addr: &SocketAddr, timeout: Duration) -> io::Result<TcpStream> |
Initiates a TCP connection with an upper time bound. |
.peer_addr() |
peer_addr(&self) -> io::Result<SocketAddr> |
Returns the remote socket address of the connected peer. |
.local_addr() |
local_addr(&self) -> io::Result<SocketAddr> |
Returns the local socket address to which this stream is bound. |
.shutdown(how) |
shutdown(&self, how: Shutdown) -> io::Result<()> |
Closes the read half, write half, or both halves of the TCP connection. |
.set_nodelay(nodelay) |
set_nodelay(&self, nodelay: bool) -> io::Result<()> |
Enables TCP_NODELAY (disabling Nagle's algorithm for low-latency writes). |
.peek(&mut buf) |
peek(&self, buf: &mut [u8]) -> io::Result<usize> |
Copies incoming bytes from the kernel receive buffer without consuming them. |
.try_clone() |
try_clone(&self) -> io::Result<TcpStream> |
Duplicates the OS socket descriptor so two handles share the same TCP connection. |
Reading, Peeking, and Half-Closing a Stream
Calling stream.shutdown(Shutdown::Write) sends a TCP FIN packet to the remote peer, signaling that the sender has finished transmitting data (so the peer's read returns 0 / EOF), while keeping the receive half open to read the peer's response.
use std::io::{self, Read, Write};
use std::net::{Shutdown, TcpListener, TcpStream};
fn main() -> io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:0")?;
let server_addr = listener.local_addr()?;
let mut client = TcpStream::connect(server_addr)?;
client.set_nodelay(true)?;
println!("TCP_NODELAY enabled: {}", client.nodelay()?);
let (mut server, _) = listener.accept()?;
// Client sends request and half-closes its write side
client.write_all(b"GET /status")?;
client.shutdown(Shutdown::Write)?;
// Server peeks at the first 3 bytes without removing them from the buffer
let mut peek_buf = [0u8; 3];
let peeked = server.peek(&mut peek_buf)?;
println!("Server peeked ({peeked} bytes): {:?}", std::str::from_utf8(&peek_buf).unwrap());
// Server reads the full stream (including the peeked bytes) until client EOF
let mut request = String::new();
server.read_to_string(&mut request)?;
println!("Server read full request: {request:?}");
// Client can still read the server's reply even after shutting down its write half!
server.write_all(b"200 OK")?;
drop(server); // Closing server sends EOF to client
let mut response = String::new();
client.read_to_string(&mut response)?;
println!("Client received response: {response:?}");
Ok(())
}
TCP_NODELAY enabled: true
Server peeked (3 bytes): "GET"
Server read full request: "GET /status"
Client received response: "200 OK"
Splitting a TcpStream for Full-Duplex Threads
Because Read and Write are also implemented for shared references &TcpStream, two scoped threads can read from and write to &stream simultaneously, or you can call stream.try_clone() to obtain an independent owned TcpStream handle pointing to the same underlying kernel socket.