Search code examples
rustchat

How to flush TcpStream


I'm trying to write a simple chat program in Rust. Currently I am stuck at trying to flush the TcpStream which does not work but I'm not even sure this is the right direction. I tried wrapping it in an Arc but was not successful. The server just receives input from the client, prints it out and relays it back to the client whom in return should print it out. With netcat I get the correct output so it should probably be flushing the TcpStream. Who can help me in the right direction?

Error:

error[E0382]: borrow of moved value: `stream`
  --> src/main.rs:26:9
   |
21 | fn listen_for_messages(stream: &mut TcpStream) {
   |                        ------ move occurs because `stream` has type `&mut TcpStream`, which does not implement the `Copy` trait
22 |     let streamreader = BufReader::new(stream);
   |                                       ------ value moved here
...
26 |         stream.flush().unwrap();
   |         ^^^^^^^^^^^^^^ value borrowed here after move

For more information about this error, try `rustc --explain E0382`.
error: could not compile `client` due to previous error

Server:

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

fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:7878")?;
    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                thread::spawn(|| {
                    println!("'{}' connected", stream.peer_addr().unwrap());
                    handle_connection(stream); 
                });
            }
            Err(e) => { println!("{e}")}
        }
    }

    Ok(())
}

fn handle_connection(mut stream: TcpStream) {
    let reader = stream.try_clone().unwrap();
    let reader = std::io::BufReader::new(reader);
    
    for response in reader.lines() {
        let response = response.unwrap();
        println!("{}", response);
        stream.write_all(response.as_bytes()).unwrap();
        stream.flush().unwrap();
    }
}

Client:

use std::net::TcpStream;
use std::thread;

fn main() {
    if let Ok(mut stream) = TcpStream::connect("127.0.0.1:7878") {
        println!("Connected");
        let mut reader = stream.try_clone().unwrap();
        thread::spawn(move || {
            listen_for_messages(&mut reader);
        });
        loop {
            write_msg(&mut stream);
        }
        
    } else {
        println!("Could not connect");
    }
}

fn listen_for_messages(stream: &mut TcpStream) {
    let streamreader = BufReader::new(stream);
    for response in streamreader.lines() {
        let response = response.unwrap();
        println!("{}", response);
        stream.flush().unwrap();               //     <---- Error
    }
}

fn write_msg(stream: &mut TcpStream) {
    let mut buf = String::new();
    io::stdin().read_line(&mut buf).unwrap();
    stream.write(buf.as_bytes()).unwrap();
    stream.flush().unwrap();
}

Solution

  • &TcpStream also implements Read and Write, so you can just pass it around. You also don't need to try_clone() the stream, you can share it with Arc:

    fn main() {
        if let Ok(stream) = TcpStream::connect("127.0.0.1:7878") {
            println!("Connected");
            let stream = Arc::new(stream);
            let reader = Arc::clone(&stream);
            thread::spawn(move || {
                listen_for_messages(&reader);
            });
            loop {
                write_msg(&stream);
            }
        } else {
            println!("Could not connect");
        }
    }
    
    fn listen_for_messages(mut stream: &TcpStream) {
        let streamreader = BufReader::new(stream);
        for response in streamreader.lines() {
            let response = response.unwrap();
            println!("{}", response);
            stream.flush().unwrap();
        }
    }
    
    fn write_msg(mut stream: &TcpStream) {
        let mut buf = String::new();
        io::stdin().read_line(&mut buf).unwrap();
        stream.write(buf.as_bytes()).unwrap();
        stream.flush().unwrap();
    }
    

    And in the server too:

    fn main() -> std::io::Result<()> {
        let listener = TcpListener::bind("127.0.0.1:7878")?;
        for stream in listener.incoming() {
            match stream {
                Ok(stream) => {
                    thread::spawn(|| {
                        println!("'{}' connected", stream.peer_addr().unwrap());
                        handle_connection(stream); 
                    });
                }
                Err(e) => { println!("{e}")}
            }
        }
    
        Ok(())
    }
    
    fn handle_connection(stream: TcpStream) {
        let reader = std::io::BufReader::new(&stream);
        
        for response in reader.lines() {
            let response = response.unwrap();
            println!("{}", response);
            (&stream).write_all(response.as_bytes()).unwrap();
            (&stream).flush().unwrap();
        }
    }