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
104: UdpSocket Basics
Up until now, we've mostly dealt with TCP or local logic. But sometimes, the overhead of a guaranteed connection is just too much. If you're building a game, a streaming service, or a simple discovery tool, you want UDP. It's "fire and forget"—you throw a packet at an IP and port, and you hope for the best. No handshakes, no sequence numbers, just raw speed.
To show you how this works, let's build a simple "Heartbeat Monitor." We'll create a server that listens for "ping" messages from a client and replies with a "pong." It's a classic pattern for checking if a remote machine is still alive without maintaining a heavy persistent connection.
Binding to a Local Port
First, we need the server to claim a spot on the network. In Rust, we use UdpSocket::bind. Unlike TCP, where you bind and then listen() for incoming connections, a UDP socket is ready to receive data the moment it's bound.
use std::net::UdpSocket;
fn main() -> std::io::Result<()> {
// I'm using 127.0.0.1 for local testing.
// Port 34253 is just a random high port I picked to avoid conflicts.
let socket = UdpSocket::bind("127.0.0.1:34253")?;
println!("Listening on 127.0.0.1:34253...");
Ok(())
}
The Buffer Trap
Now we need to actually wait for data. recv_from is the tool here. It takes a mutable slice (the buffer) and returns how many bytes were actually written into that buffer, along with the address of the sender.
I'll be honest: every time I start a new UDP project, I almost make the same mistake with the buffer. Watch what happens when I try to print the received message:
let mut buf = [0u8; 1024]; // A 1KB buffer
let (amt, src) = socket.recv_from(&mut buf)?;
// MISTAKE: Printing the whole buffer
println!("Received: {:?}", buf);
If the client sends the word "Ping" (4 bytes), the code above will print "Ping" followed by 1,020 zeros. It's a common annoyance. We have to use the amt variable—the number of bytes actually received—to slice the buffer correctly. Let's fix that:
let (amt, src) = socket.recv_from(&mut buf)?;
let filled_buf = &buf[..amt];
println!("Received {} bytes from {}: {:?}", amt, src, filled_buf);
Sending the Response
Since UDP is connectionless, the socket doesn't "know" who it's talking to. That's why recv_from gives us the src address. If we want to reply, we use send_to, passing in the data and that specific address.
Here is the complete server logic. I'm wrapping it in a loop because we want this monitor to run indefinitely.
use std::net::UdpSocket;
fn main() -> std::io::Result<()> {
let socket = UdpSocket::bind("127.0.0.1:34253")?;
let mut buf = [0u8; 1024];
loop {
let (amt, src) = socket.recv_from(&mut buf)?;
let msg = std::str::from_utf8(&buf[..amt]).unwrap_or("Invalid UTF-8");
println!("Heartbeat from {}: {}", src, msg);
if msg == "ping" {
socket.send_to(b"pong", &src)?;
}
}
}
Wiring up the Client
The client is even simpler. Interestingly, a UdpSocket on the client side still needs to be "bound," but since we don't care which port the OS assigns to us for sending, we bind to port 0. This tells the system, "just give me any available port."
use std::net::UdpSocket;
fn main() -> std::io::Result<()> {
// Bind to 0 to let the OS choose a random available port
let socket = UdpSocket::bind("127.0.0.1:0")?;
// We don't "connect", we just send to the destination
socket.send_to(b"ping", "127.0.0.1:34253")?;
println!("Ping sent!");
let mut buf = [0u8; 1024];
let (amt, src) = socket.recv_from(&mut buf)?;
println!("Response from {}: {:?}", src, &buf[..amt]);
Ok(())
}
When you run the server first and then the client, you'll see the handshake happen. Remember: if the server isn't running, the client's send_to will still succeed. That's the nature of UDP—it doesn't check if the destination is actually listening. It just launches the packet into the void.
📋 Practical Task
Build a UDP-based System Status Broadcaster
Your task is to create a small system that simulates a sensor reporting its status to a monitoring server. Instead of a simple "ping/pong," implement the following:
- The Broadcaster (Client): Create a loop that sends a string in the format
"STATUS: OK"or"STATUS: ERROR"every 2 seconds to127.0.0.1:4000. - The Monitor (Server): Bind to
127.0.0.1:4000. It should receive the message and, if it sees"STATUS: ERROR", print a loud warning to the console:"!!! ALERT: System Failure Detected from [Source Address] !!!". Otherwise, it should just print"System Healthy".
Make sure you handle the buffer slicing correctly so you aren't comparing a 1024-byte array against a short string!
There are no comments for now.