Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
103: TcpListener and TcpStream
Think of TcpListener and TcpStream like a hotel receptionist. The receptionist doesn't actually go on vacation with the guests or carry their luggage; their entire job is to sit at a desk, wait for someone to walk through the front door, and then hand that person off to a bellhop who can actually help them. The receptionist is the "Listener," and the conversation between the bellhop and the guest is the "Stream."
In Rust, it works exactly like that. You don't use a TcpListener to send data. You use it to wait for a connection. Once a connection is established, the listener gives you a TcpStream, which is the actual bidirectional pipe you use to read and write bytes.
Setting up the front desk
Before you can accept connections, you have to "bind" your listener to a specific address and port. This is basically telling the operating system, "Any traffic coming in on port 7878 belongs to me."
use std::net::TcpListener;
fn main() {
// We bind to localhost on port 7878.
// If another app is using this port, this will panic.
let listener = TcpListener::bind("127.0.0.1:7878").expect("Could not bind to port");
println!("Listening for connections...");
}
I usually use expect here because if you can't bind to your port, your server is dead in the water anyway. There's no point in trying to recover gracefully if you can't even open the front door.
Handing off the guest
Now that the listener is active, you need to actually handle people arriving. The incoming() method returns an iterator that blocks the current thread until a client connects. When it does, it yields a TcpStream.
for stream in listener.incoming() {
match stream {
Ok(stream) => {
println!("New connection established!");
handle_connection(stream);
}
Err(e) => {
eprintln!("Connection failed: {}", e);
}
}
}
Notice how I'm calling a separate function, handle_connection. If you put all your logic inside that for loop, your server can only handle one person at a time. While you're talking to one guest, everyone else is stuck standing in the lobby. In a real production app, you'd likely spawn a thread or use async for each stream, but for now, let's focus on the communication itself.
Talking through the pipe
The TcpStream implements the Read and Write traits. This means you treat it similarly to how you'd treat a file. You read bytes into a buffer and write bytes back out. One thing to keep in mind: TCP is a stream of bytes, not a stream of "messages." There are no inherent boundaries. If a client sends "Hello", you might receive "Hel" and then "lo" in two separate reads.
Here is a complete example of a server that reads a request and sends back the number of bytes it received—a simple "Echo-Length" server.
use std::net::{TcpListener, TcpStream};
use std::io::{Read, Write};
fn handle_connection(mut stream: TcpStream) {
let mut buffer = [0; 1024]; // A 1KB buffer to hold incoming data
// Read data from the stream into our buffer
match stream.read(&mut buffer) {
Ok(size) => {
let received_data = &buffer[..size];
let response = format!("I received {} bytes of data from you!", size);
// Write the response back to the client
if let Err(e) = stream.write_all(response.as_bytes()) {
eprintln!("Failed to send response: {}", e);
}
}
Err(e) => eprintln!("Failed to read from stream: {}", e),
}
}
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
if let Ok(s) = stream {
handle_connection(s);
}
}
}
I used write_all instead of write here. I highly recommend this. write doesn't guarantee that the entire buffer is sent in one go; write_all keeps pushing until everything is gone or an error occurs. It saves you from writing a manual loop to ensure the full message was sent.
📋 Practical Task
Build a Remote System-Status Ping Server
Your task is to create a server that acts as a status monitor. Instead of echoing the length of the message, the server should respond based on the content of the message received.
- Requirement 1: The server must bind to
127.0.0.1:8080. - Requirement 2: If the client sends the exact string
"STATUS", the server should respond with"System Operational". - Requirement 3: If the client sends
"TIME", the server should respond with"Current time: [Insert any dummy time string here]". - Requirement 4: For any other input, the server should respond with
"Unknown Command". - Requirement 5: Ensure you are using
std::io::Readandstd::io::Writeto handle the stream.
Hint: Remember that the buffer may contain trailing null bytes or whitespace. You'll want to convert the slice of the buffer you actually read into a string and trim it before comparing.
There are no comments for now.