← Back to Blog

Simple UDP Implementation in Rust

Published: July 6, 2025
Tags: rust, beginner-friendly, networking, udp

Introduction

This article demonstrates how to implement UDP networking in Rust, providing a practical introduction to network programming concepts.

System Specifications

MacBook Air M2 arm64

Background Knowledge

What is UDP?

UDP (User Datagram Protocol) is a transport layer protocol in the OSI reference model.

OSI Model

In the transport layer, there are two representative protocols: TCP and UDP.

Neither TCP nor UDP is superior; they serve different purposes:

The choice depends on whether real-time performance or reliable delivery is more important.

UDP Packet Structure

UDP packets have a simple structure:

UDP Packet Structure

The UDP header contains:

Setup

Docker Environment

docker run --rm -it --name rust-udp -v "$PWD":/app rust:1.78 bash
# Inside container
apt-get update
apt-get install -y vim

cd /app
cargo new --bin udp_demo
cd udp_demo

Implementation

File Preparation

Server Side

mkdir -p src/bin

cat <<'EOF' > src/bin/server.rs
use std::net::UdpSocket;

fn main() -> std::io::Result<()> {
    // Bind to localhost port 8080
    let socket = UdpSocket::bind("127.0.0.1:8080")?;
    println!("UDP Server listening on 127.0.0.1:8080");

    let mut buffer = [0; 1024];
    
    loop {
        // Receive data from client
        match socket.recv_from(&mut buffer) {
            Ok((size, src)) => {
                let received = String::from_utf8_lossy(&buffer[..size]);
                println!("Received from {}: {}", src, received);
                
                // Echo back to client
                let response = format!("Echo: {}", received);
                socket.send_to(response.as_bytes(), src)?;
            }
            Err(e) => {
                eprintln!("Error receiving data: {}", e);
            }
        }
    }
}
EOF

Client Side

cat <<'EOF' > src/bin/client.rs
use std::net::UdpSocket;
use std::io::{self, Write};

fn main() -> std::io::Result<()> {
    // Create UDP socket
    let socket = UdpSocket::bind("127.0.0.1:0")?;
    let server_addr = "127.0.0.1:8080";
    
    println!("UDP Client connected to {}", server_addr);
    println!("Type messages to send (Ctrl+C to exit):");

    loop {
        // Get user input
        print!("> ");
        io::stdout().flush()?;
        
        let mut input = String::new();
        io::stdin().read_line(&mut input)?;
        let message = input.trim();
        
        if message.is_empty() {
            continue;
        }
        
        // Send message to server
        socket.send_to(message.as_bytes(), server_addr)?;
        
        // Receive response
        let mut buffer = [0; 1024];
        match socket.recv_from(&mut buffer) {
            Ok((size, _)) => {
                let response = String::from_utf8_lossy(&buffer[..size]);
                println!("Server response: {}", response);
            }
            Err(e) => {
                eprintln!("Error receiving response: {}", e);
            }
        }
    }
}
EOF

Testing the Implementation

Building and Running

# Build both binaries
cargo build

# Terminal 1: Start server
cargo run --bin server

# Terminal 2: Start client
cargo run --bin client

Example Session

# Server output:
UDP Server listening on 127.0.0.1:8080
Received from 127.0.0.1:54321: Hello UDP!
Received from 127.0.0.1:54321: How are you?

# Client output:
UDP Client connected to 127.0.0.1:8080
Type messages to send (Ctrl+C to exit):
> Hello UDP!
Server response: Echo: Hello UDP!
> How are you?
Server response: Echo: How are you?

Key Concepts Demonstrated

UDP Socket Operations

Error Handling

Rust's Result type ensures proper error handling:

match socket.recv_from(&mut buffer) {
    Ok((size, src)) => { /* handle success */ }
    Err(e) => { /* handle error */ }
}

Memory Safety

Buffer operations are bounds-checked, preventing overflow vulnerabilities common in C networking code.

Advantages of UDP

Disadvantages of UDP

Conclusion

This UDP implementation in Rust demonstrates the language's strengths in network programming: memory safety, clear error handling, and clean APIs. While UDP lacks the reliability of TCP, its simplicity and performance make it ideal for real-time applications where speed is more important than guaranteed delivery.

Rust's standard library provides excellent abstractions for network programming while maintaining the performance characteristics needed for systems-level networking code.