Tutorial: Chat Application (Client-Server) in Rust
Tags: rust, rustseries
Published: 2024-05-12 09:46:05

---
title: Tutorial: Chat Application (Client-Server) in Rust
published: true
date: 2024-05-10 10:32:58 UTC
tags: Rust,rustlang,rustseries
canonical_url: https://eleftheriabatsou.hashnode.dev/tutorial-chat-application-client-server-in-rust
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rbmeodzzugqhvj0nrq9i.jpeg
---

Hello, amazing people and welcome back to my blog! Today we're going to learn how to make a simple chat application in Rust. The application will have two parts: 1. Client, 2.Server. You'll be able to type something on the client side and the server will receive it.

{% embed https://twitter.com/BatsouElef/status/1788949895014932776 %}

## Server

Create a folder with two Rust projects, one folder will be the client and the other one the server.

On the server folder, and in my terminal I'm going to type `cargo new` and open my code editor. In the `.toml` file you don't have to change anything so let's start with importing stuff in the `.main`.

We're going to need `standard io` with the `ErrorKind`, which is an error message type, then we're going to need the `Read` and `Write` . We want to bring in `standard net` `TcpListener`, this will allow us to create our server and listen on a port. We also want to bring in our `standard sync` `mpsc`, which will allow us to spawn a channel. Finally, we want to bring in `standard thread` so that we can work with multiple threads.

```
use std::io::{ErrorKind, Read, Write};
use std::net::TcpListener;
use std::sync::mpsc;
use std::thread;

```

Next, we want to create two constants. The first one is called `LOCAL` and it will have our local host with a port in it and the second constant is called `MSG_SIZE` and this will be the buffer size of our messages, we want our messages to be at most 32 bit in size (of course you can experiment with it, and make it longer if you like).

```
const LOCAL: &str = "127.0.0.1:6000";
const MSG_SIZE: usize = 32;

```

### Main Function

Inside the `main()` let's instantiate our server by saying `let server = TcpListener::bind(LOCAL).expect("Listener failed to bind");`. If it fails to bind, then we're going to return this message `"Listener failed to bind"` inside of a panic. Next, we want to push our server into what is called non-blocking mode `server.set_nonblocking(true).expect("failed to initialize non-blocking");` and if this fails, then we're going to print out this string `"failed to initialize non-blocking"` inside of a panic. The non-blocking mode basically lets our server constantly check for messages.

```
fn main() {
    let server = TcpListener::bind(LOCAL).expect("Listener failed to bind");
    server.set_nonblocking(true).expect("failed to initialize non-blocking");

```

We want to create a mutable vector called `clients` that will allow us to put all of our clients, that way we can have multiple clients connecting to the server at once rather than just one or two. Then we want to instantiate our channel and assign it to a string type.

```
let mut clients = vec![];
let (tx, rx) = mpsc::channel::<String>();

```

Let's create a `loop` and inside of it let's add `if let Ok((mut socket, addr)) = server.accept()`. The `server.accept` is what allows us to accept connections to the server. If we get an okay, then it worked out.

```
if let Ok((mut socket, addr)) = server.accept() {
   println!("Client {} connected", addr);

```

Then we want to clone our `tx` or transmitter, we want to take our `socket`, try to `clone` it, and then `push` it into our clients vector. If this comes back and fails, then we're going to `panic`. The reason we're cloning our socket is so that we can push it into our thread.

```
let tx = tx.clone();
clients.push(socket.try_clone().expect("failed to clone client"));

```

Then we want to `spawn` our `thread` with a `move` closure inside of it and the first thing we do inside this `move` closure is to create another `loop`. We want to create a mutable buffer, which will be a `vector` with zeros inside of it with the message size.

```
        thread::spawn(move || loop {
                let mut buff = vec![0; MSG_SIZE];

                match socket.read_exact(&mut buff) {
                    Ok(_) => {
                        let msg = buff.into_iter().take_while(|&x| x != 0).collect::<Vec<_>>();
                        let msg = String::from_utf8(msg).expect("Invalid utf8 message");

                        println!("{}: {:?}", addr, msg);
                        tx.send(msg).expect("failed to send msg to rx");
                    }, 
                    Err(ref err) if err.kind() == ErrorKind::WouldBlock => (),
                    Err(_) => {
                        println!("closing connection with: {}", addr);
                        break;
                    }
                }

                sleep();
            });

```

We want to `match socket.read_exact(&mut buff)` and this will read our message into our buffer. We're going to assign `msg` to our `buff.into_iter()` . We're taking the message that we received, converting it into an iterator, and taking all of the characters that are not white space and collecting them inside our vector. Finally, we want to print out the `address` sent the `message`.

We're going to send our message through our transmitter to our receiver and if this fails, then we're going to `panic` and send back `"failed to send msg to rx"`.

We're also going to check the actual error inside of our error and if `err.kind() == ErrorKind::WouldBlock`, is equal to an error that would block our non-blocking, then we want to send back a unit type. Otherwise, we want to check for another error and if we get an error, we don't care about what's inside of it, we simply say `"closing connection with: {}"` our client. Finally, break out of the loop.

### Sleep Function

Alright, so we can leave the code as it is, but the problem is that our thread would be constantly looping around and it would be awkward. Let's create something that will allow our `loop` to sort of rest while it's not receiving messages. To do so, we have a new function called `sleep` which will allow our `thread` to sleep for a moment, and we can call it passing the time duration. (As you saw above it we call it from `main()`.)

```
fn sleep() {
    thread::sleep(::std::time::Duration::from_millis(100));
}

```

**Back to** `main()` **:**

We want to do another `if let Ok(msg) = rx.try_recv()`, this is for when our server receives a message through the channel. We want to collect all of the messages that we get through our channel and `clients = clients.into_iter().filter_map(|mut client|` , so our mutable vector set the `buffer` equal to `msg.clone().into_bytes();`. We're going to convert our messages into bytes, and then `resize` that buffer based on our message size, and finally, we're going to take our client and we're going to `write_all` of the entire `buffer`.

Last but not least, we're going to `map` it into our `client` and send it back. Then, we're going to `collect` it all into a `vector`.

This is all the code that we need for our server!

```
        if let Ok(msg) = rx.try_recv() {
            clients = clients.into_iter().filter_map(|mut client| {
                let mut buff = msg.clone().into_bytes();
                buff.resize(MSG_SIZE, 0);

                client.write_all(&buff).map(|_| client).ok()
            }).collect::<Vec<_>>();
        }

```

## Client

Let's go to our client folder. Once again in the `.toml` file, we don't need any dependencies, but we do need to import a few things in the `main.rs`. Firstly, from the `std::io` we're importing `self` because we want to import the IO library itself and then we are going to import `ErrorKind`, `Read` and `Write`. We're going to import `net::Tcp Stream` , `sync::mpsc::{self, TryRecvError}` , `thread` and finally the standard library `time` `Duration`.

```
use std::io::{self, ErrorKind, Read, Write};
use std::net::TcpStream;
use std::sync::mpsc::{self, TryRecvError};
use std::thread;
use std::time::Duration;

```

Again, we want 2 constants here similar to the `main.rs` ones from the server side.

```
const LOCAL: &str = "127.0.0.1:6000";
const MSG_SIZE: usize = 32;

```

### Main Function

Let's start working on the `main()`. We want to create a mutable `client`, which is a `TcpStream` and then we're going to `connect` it to `LOCAL` . If it doesn't work, we're going to `panic` and say `"Stream failed to connect"`. We also want our client to be `nonblocking` so we're going to set the flag nonblocking to `true`. If it fails, then we're going to `panic` with `"failed to initiate non-blocking"`.

```
fn main() {
    let mut client = TcpStream::connect(LOCAL).expect("Stream failed to connect");
    client.set_nonblocking(true).expect("failed to initiate non-blocking");

```

Next, we want to instantiate our channel, for that we're going to be passing strings (like we did in the server).

```
let (tx, rx) = mpsc::channel::<String>();

    thread::spawn(move || loop {
        let mut buff = vec![0; MSG_SIZE];
        match client.read_exact(&mut buff) {
            Ok(_) => {
                let msg = buff.into_iter().take_while(|&x| x != 0).collect::<Vec<_>>();
                println!("message recv {:?}", msg);
            },
            .
.
.
.

```

We want to `spawn` our `thread` and we want to create a `move` closure inside of it with a `loop`. We're going to create a mutable `buffer` with a `vector` with zeros and the message size. Then we want to `match` on [`client.read`](http://client.read)`_exact(&mut buff)`, and read our message through the buffer. If we get back an `Ok(_)`, we want to say `let msg = buff.into_iter().take_while(|&x| x != 0).collect::<Vec<_>>();` which turns it into an iterator, and checks if the references inside of it is equal to zero. Then we're going to collect all of them inside of our vector (all the ones that are equal to zero, are going to be discarded).

```
        Err(ref err) if err.kind() == ErrorKind::WouldBlock => (),
            Err(_) => {
                println!("connection with server was severed");
                break;
            }
        }

```

Then we want to print out the message we received. Same as we did earlier we want to check if the `err.kind` is `ErrorKind::WouldBlock`, and then we're going to send back a unit type if it is. If we get another type of error, we're just going to break out of our loop. Before that, we're going to print `println!("connection with server was severed");`.

```
        match rx.try_recv() {
            Ok(msg) => {
                let mut buff = msg.clone().into_bytes();
                buff.resize(MSG_SIZE, 0);
                client.write_all(&buff).expect("writing to socket failed");
                println!("message sent {:?}", msg);
            }, 
            Err(TryRecvError::Empty) => (),
            Err(TryRecvError::Disconnected) => break
        }

        thread::sleep(Duration::from_millis(100));

```

Then we want to `match rx.try_recv()` so we want to see if the server sends back a message that says that it got the message from the client. If we do get back that message as an okay, then we want to clone it into bytes and put it inside of a `buff` variable like this: `let mut buff = msg.clone().into_bytes();`. We also want to resize our buffer by our message size and we want to write all of our buffers into our client `client.write_all(&buff)`, If they fail, we're going to say `expect("writing to socket failed");`. Otherwise, we're going to print out that we sent the message and the message itself `"message sent {:?}", msg.`

We also want to check if `Err(TryRecvError::Empty) => (),` is empty and if it is, then we're just going to send back a unit type. Then we want to check if it's a disconnected type `Err(TryRecvError::Disconnected) => break`, in which case we want to break the loop.

Then like we did before, we want to have our thread `sleep` for a 100 milliseconds.

```
    println!("Write a Message:");
    loop {
        let mut buff = String::new();
        io::stdin().read_line(&mut buff).expect("reading from stdin failed");
        let msg = buff.trim().to_string();
        if msg == ":quit" || tx.send(msg).is_err() {break}
    }
    println!("bye bye!");

```

Last but not least, outside of our `thread` and everything, we want to create a print statement. We will ask the user to `Write a message:` and we'll create a new mutable string inside a `loop`. We want to read into that string from our standard input so essentially when the user types in something from the console, we want to read that and have it as a `string`. We're doing all of this in a `loop` so that the user can type multiple messages back to back. We also want to take our buffer, trim it and then use the `to_string` method to put it into a message variable: `let msg = buff.trim().to_string();.` Then we'll say `if msg == ":quit" || tx.send(msg).is_err() {break}`, we'll break out of our `loop` and we'll print out `bye bye!`.

That's all! If you're still with me, we did it! Now, let's run it. 🏃

## How to Run it

To run this program you need to open 2 terminals. One for the `client` and one for the `server`.

- In the server run `cargo run`.

- Then do the same for your client. And this time you should see a message, `write a message`.

- Type something and then you should see that in the server as well as the bytes received.

- For example if you typed "hello", you should see in the server side "hello" in bytes too. For longer messages, it will cut them off at 32 bytes.

- If you type `:quit` then the program will quit on the client and the server will say `closing connection` and the `client name`.

You can find all the code on [GitHub](https://github.com/EleftheriaBatsou/chat-app-client-server-rust/tree/main).

{% embed https://github.com/EleftheriaBatsou/chat-app-client-server-rust %}

Happy Rust Coding! 🤞🦀


* * *

👋 Hello, I'm Eleftheria, **Community Manager,** developer, public speaker, and content creator.

🥰 If you liked this article, consider sharing it.

🔗 [**All links**](https://limey.io/batsouelef) | [**X**](https://twitter.com/BatsouElef) | [**LinkedIn**](https://www.linkedin.com/in/eleftheriabatsou/)