Rust TCP Basics
TCP is a connected stream of bytes between two programs. Each accepted connection is a TcpStream.
Open a server socket
bind claims the port. local_addr reports the one the OS picked.
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();Accept one client
accept waits for a client and hands you the stream and their address.
let (stream, addr) = listener.accept().unwrap();Connect as a client
connect opens a stream to the server's address.
let stream = TcpStream::connect(("127.0.0.1", port)).unwrap();Streams are read and written like files
write_all sends. read_to_string waits for the reply.
client.write_all(b"hello tcp").unwrap();
let mut reply = String::new();
client.read_to_string(&mut reply).unwrap();Example
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
let server = thread::spawn(move || {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
tx.send(listener.local_addr().unwrap().port()).unwrap();
let (mut stream, _addr) = listener.accept().unwrap();
let mut received = String::new();
stream.read_to_string(&mut received).unwrap();
let reply = received.to_uppercase();
stream.write_all(reply.as_bytes()).unwrap();
received
});
let port = rx.recv().unwrap();
let mut client = TcpStream::connect(("127.0.0.1", port)).unwrap();
client.write_all(b"hello tcp").unwrap();
client.shutdown(std::net::Shutdown::Write).unwrap();
let mut reply = String::new();
client.read_to_string(&mut reply).unwrap();
println!("reply = {reply}");
let received = server.join().unwrap();
println!("server received = {received}");
}
Key points
- TCP lives in std::net.
- A TcpListener accepts clients.
- Each TcpStream is one live conversation.
- A TcpStream implements Read and Write.
- So it behaves like a file made of network.
- Use TcpListener::bind("127.0.0.1:0") to take the first free port.
- listener.local_addr() then tells you which port the operating system picked.
- Use TcpStream::connect(("127.0.0.1", port)) to open the client side.
- ToSocketAddrs also accepts names.
- So connect("example.com:80") resolves the name first.
- A TCP stream carries bytes, not messages.
- Agree on an ending rule yourself.
- This lesson closes the sending half with shutdown(Shutdown::Write).
- That says "I am done writing".
- The lesson then reads until the stream closes.
- Use stream.set_read_timeout(Some(Duration::from_secs(2))) when a read must not wait forever.
- The classic std pattern is one thread per client.
- So many clients can be served at once.
- std::net needs no extra crates and no Tokio runtime for a small TCP server.
The closing program runs a server thread and a client in main. They talk on the loopback address. The client sends hello tcp. The server replies in uppercase. Main prints the reply, then the server's received line.
Note
127.0.0.1 is this computer itself.
Every example here talks to its own machine.
That is the safe place to learn.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
What does binding a TcpListener to port 0 do?