Sling Academy
Home/Rust/Building a Simple TCP Echo Server in Rust

Building a Simple TCP Echo Server in Rust

Last updated: January 06, 2025

Building network applications might seem intimidating at first, especially when diving into a systems programming language like Rust. However, Rust's rich ecosystem provides numerous crates and libraries which simplify the task. In this article, we will walk through building a simple TCP echo server using Rust. An echo server listens for incoming connections, reads data from the client, and sends the same data back to the client.

Setting Up the Environment

First, we need to have Rust installed on our machine. If you haven't done so, you can install it from the official website here. After installation, it is also good to have cargo, Rust's package manager, available which should come bundled with Rust.

Creating a new Rust project

We will start by creating a new Rust project. Open a terminal window and run the following command:

cargo new tcp_echo_server --bin

This command will create a new directory named tcp_echo_server with a basic Rust project template. Navigate into this directory:

cd tcp_echo_server

Writing the TCP Echo Server

Open the main.rs file found in the src directory. Replace its contents with the code below:


use std::net::{TcpListener, TcpStream};
use std::io::{Read, Write};

fn handle_client(mut stream: TcpStream) {
    let mut buffer = [0; 512];
    loop {
        match stream.read(&mut buffer) {
            Ok(0) => break, // Connection was closed
            Ok(n) => {
                // Echo everything received back to the client
                if let Err(e) = stream.write_all(&buffer[..n]) {
                    eprintln!("Failed to send response: {}", e);
                    break;
                }
            }
            Err(e) => {
                eprintln!("Failed to read from socket: {}", e);
                break;
            }
        }
    }
}

fn main() -> std::io::Result<()> {
    // Listening on port 7878
    let listener = TcpListener::bind("127.0.0.1:7878")?;
    println!("Server listening on port 7878");

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                std::thread::spawn(move || {
                    handle_client(stream);
                });
            }
            Err(e) => {
                eprintln!("Connection failed: {}", e);
            }
        }
    }
    Ok(())
}

We begin by using the standard library's TcpListener to listen for incoming connections on TCP port 7878. When a connection is made, listener.incoming() returns an iterator over streams (instances of TcpStream), and we handle each connection in a new thread.

The handle_client function is where the core echo logic resides. It reads data into a buffer using stream.read(), and then writes the same data back using stream.write_all(). If a read returns zero bytes, the client closed the connection.

Building and Running the Server

Now let's build and run our echo server. In the terminal, while inside the project directory, execute the following:

cargo run

This will compile and start the server. The server is now running and listening on 127.0.0.1:7878.

Testing the Server

To test the echo server, you can use any TCP client, but netcat is easiest. Open another terminal and execute:

nc localhost 7878

Type anything into the terminal, press Enter, and you'll see the same message echoed back to you.

Conclusion

This simple Rust project demonstrates how to utilize TCP sockets to build a basic echo server. Even though the application is simple, the building blocks used here are the foundations of many network applications – listening for connections, handling connections in threads, reading from and writing to TCP streams.

Next Article: Creating a Chat Application in Rust with TCP Sockets

Previous Article: Implementing TLS/SSL in Rust with native-tls or rustls

Series: Networking in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior