Sling Academy
Home/Rust/Creating a Chat Application in Rust with TCP Sockets

Creating a Chat Application in Rust with TCP Sockets

Last updated: January 06, 2025

Creating a chat application is a classic networking exercise that can be incredibly satisfying to implement. In this tutorial, we'll dive into building a basic chat application using Rust and TCP sockets. Rust, known for its safety and performance, is a great choice for such a project due to its powerful concurrency model and guarantees of memory safety without needing a garbage collector.

Understanding TCP Sockets

TCP (Transmission Control Protocol) is a connection-oriented protocol that provides reliable, ordered, and error-checked delivery of a stream of bytes. TCP is widely used for comfortable and secure data communication, making it ideal for a chat application.

Setting Up Your Rust Project

First, ensure you have Rust installed on your machine. Rust can be installed from The Rust Programming Language official website.

Start by setting up a new Rust project:

cargo new chat_app --bin

This command will create a new directory named chat_app with a basic Rust project structure.

Dependencies

Our chat application will utilize Rust’s standard library for handling TCP connections. There's no need for additional dependencies for the basic version, but you might consider using crates for more advanced features.

Building the Server

The server will handle incoming client connections and broadcast messages to all connected clients. We'll start by importing necessary libraries:

use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::{Arc, Mutex};
use std::thread;

Next, let's initialize the server:


fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
    let clients = Arc::new(Mutex::new(Vec::new()));

    for stream in listener.incoming() {
        let stream = stream.unwrap();
        let clients = Arc::clone(&clients);

        thread::spawn(move || {
            handle_client(stream, clients);
        });
    }
}

Here, we’ve bound our server to localhost on port 7878. Incoming connections are handled in separate threads which utilize Arc and Mutex to safely share a list of client streams across threads.

The handle_client function reads messages from a single client:


fn handle_client(mut stream: TcpStream, clients: Arc<Mutex<Vec<TcpStream>>>) {
    
    clients.lock().unwrap().push(stream.try_clone().unwrap());
    
    let mut buffer = [0; 512];

    loop {
        let bytes_read = stream.read(&mut buffer).unwrap();
        if bytes_read == 0 {
            return;
        }

        let message = String::from_utf8_lossy(&buffer[..bytes_read]).into_owned();
        println!("Message received: {}", message);

        let clients_guard = clients.lock().unwrap();
        for client in clients_guard.iter() {
            client.write_all(message.as_bytes()).unwrap();
        }
    }
}

This exports each message received from one client to all clients, essentially forming the conversation channel.

Building the Client

Our client will connect to the server and send user inputs as messages:


use std::io::{self, Write};
use std::net::TcpStream;

fn main() {
    let mut stream = TcpStream::connect("127.0.0.1:7878").unwrap();
    println!("Connected to the server!");

    let mut input = String::new();
    loop {
        io::stdin().read_line(&mut input).unwrap();
        stream.write_all(input.as_bytes()).unwrap();
        input.clear();
    }
}

In this client application, we connect to our server by binding to the same IP address and port. We then enter a loop to gather input and send it to the server.

Running the Application

Run the server with:

cargo run

In another terminal, run multiple clients (also using cargo run) to begin chatting between them.

Conclusion

You've now implemented a simple yet functional chat application using Rust and TCP sockets. While this is a basic implementation, it forms the foundation for more sophisticated features like handling concurrency in non-blocking sockets, chat rooms, and authentication. As an exercise, consider extending this application with more features to gain deeper insights into network programming with Rust.

Next Article: Using UDP in Rust for Lightweight Message Transmission

Previous Article: Building a Simple TCP Echo Server in Rust

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