Search code examples
rustserverclient

Server TCP does not register the second incoming stream


I'm trying to make a client-server application with the server handling the connections. The server's job is to register the users that connect and to forward messages from a user (thread) to another. I'm not sure if I have explained it correctly, I'm happy to clarify any question

Server code

use std::collections::HashMap;
use std::hash::Hash;
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::path::Component::ParentDir;
use std::string::ToString;
use std::sync::{Arc, mpsc, Mutex};
use std::sync::mpsc::Receiver;
use std::thread;
use std::thread::spawn;

const GET_REQ: &str = "GET / HTTP/1.1\r\n";

fn spawn_thread(mut stream: TcpStream, uuid: String, reciver: Arc<Mutex<Receiver<String>>>){
    thread::spawn(move || {
        let reciver = reciver.lock().unwrap();
        let userID = uuid;
        println!("created new user: {userID}");
        loop {
            match reciver.recv() {
                Ok(message) => {
                    println!("{message}");
                    let splitted_message = split_message(&message);
                    println!("{:?}", splitted_message);
                    let end_user = splitted_message.get(2);
                    match end_user {
                        Some(end_user) => {
                            print!("tf ");
                            if end_user == &userID {
                                println!("woah");
                                stream.write(splitted_message.get(1).unwrap().as_bytes()).unwrap();
                            }
                        }
                        None => { println!("frick"); }
                    }
                }
                Err(_) => {
                    println!("wtf");
                    continue;
                }
            }
            print!("a");
        }
    });
}
fn split_message(messaggio: &String) -> Vec<String>{
    messaggio.split("\n").map(|x| x.to_string()).collect::<Vec<String>>()
}
fn person_already_connected(uuid: String, connected_ppl: &HashMap<String, bool>) -> bool{
    connected_ppl.get(&*uuid).is_some()
}
fn get_uuid(buffer: &String) -> String{
    let mut parameters: String = buffer.split("\r\n").filter(|x| x.contains("X-user-id")).collect();
    parameters = parameters.split(" : ").nth(1).unwrap().to_string();
    parameters
}
fn get_buffer_from_stream(stream: &mut TcpStream) -> String{
    let mut buffer = [0; 1024];
    stream.read(&mut buffer).unwrap();
    let buffer: String = String::from_utf8_lossy(&buffer).to_string();
    buffer
}
fn add_person_to_map(uuid: String, mappa: &mut HashMap<String, bool>){
    mappa.insert(uuid, true);
}

fn main (){
    let mut listener = TcpListener::bind("0.0.0.0:7878").unwrap();
    // listener.set_nonblocking(true).expect("it blocks");
    let ( mut sender, mut reciver) = mpsc::channel::<String>();
    let mut reciver = Arc::new(Mutex::new(reciver));
    let mut connected: HashMap<String, bool> = HashMap::new();
    for stream in listener.incoming(){
        match stream {
            Ok(mut stream) => {
                println!("recived stream");
                let buffer = get_buffer_from_stream(&mut stream);
                println!("buffer: {:?}", buffer);
                if buffer.starts_with(GET_REQ) && !person_already_connected(get_uuid(&buffer), &connected) {
                    add_person_to_map(get_uuid(&buffer), &mut connected);
                    let reciver = Arc::clone(&reciver);
                    spawn_thread(stream, get_uuid(&buffer), reciver);
                }
                else {
                    println!("sent stream");
                    sender.send(buffer).unwrap();
                }
            }
            _ => {continue}
        }

    }
}

client code:

use std::fmt::format;
use std::net::{TcpStream};
use std::io::{Read, Write};
use std::str::from_utf8;
use std::thread;
use std::time::Duration;
use uuid::Uuid;

fn main() {
    let uuid = Uuid::new_v4();
    let get_req =
        format!("GET / HTTP/1.1\r\nHost: yeahyeah\r\nAccept: */*\r\nX-user-id : {uuid}\r\n");

    match TcpStream::connect("myIp:7878") {
        Ok(mut stream) => {
            println!("Successfully connected to server in port 7878");

            let msg = format!("{uuid}\nplease work please please\n{uuid}");

            stream.write(get_req.as_bytes()).unwrap();
            thread::sleep(Duration::from_millis(100));
            println!("after the sleep");
            stream.write(msg.as_bytes()).unwrap();
            println!("Sent Hello, awaiting reply...");

            let mut data = [0 as u8; 1024]; // using 6 byte buffer
            match stream.read(&mut data) {
                Ok(_) => {
                    println!("text recived: {}", String::from_utf8_lossy(&data))
                },
                Err(e) => {
                    println!("Failed to receive data: {}", e);
                }
            }
        },
        Err(e) => {
            println!("Failed to connect: {}", e);
        }
    }
    println!("Terminated.");
}

I tried cloning the stream before sending it to the thread but it didn't work, I also tried to add a response from the server to the client for the first stream sent bit it didn't work either. I honestly have no clue why it doesn't read the second stream I send


Solution

  • You have a deadlock: once the first user is connected, you acquire the lock for the channel, and never release it. When the second user connects, it tries to acquire the lock but it cannot because it already acquired, so it waits. But you never release the lock (you do only in one case: if there is a message from this user but it disconnected. The write() will fail and the unwrap() will panic. But this doesn't happen). So you're waiting on a closed connection for messages that will never come, while the second user is waiting forever.

    But your program is incorrect anyway: a MPSC channel with a lock (which is incorrect thing to do always, you need to use a MPMC channel) will send the messages to one user: you may get lucky and this will be the user the message is targeting, and you may not and the targeted user will never get the message, it will just disappear. You need to use a brodcast channel, or better, keep a hashmap of UUID to channels and send the message directly to the correct user.