Search code examples
rustservertcpnetwork-programming

Reading a line in a BufReader creates an infinite loop


I'm trying to receive a message (server side) on the network using TcpListener in Rust. Here's the server code:

// Instanciate TcpListener
let server = TcpListener::bind("0.0.0.0:52001").expect("Could not bind");

match server.accept() { // Waiting new connection from a client
    Ok((stream, addr)) => { 
        println!("New connection from {}", addr);

        // Wrapping the stream in a BufReader
        let mut reader = BufReader::new(&stream); 
        let mut message = String::new();

        loop { // Entering the loop when a client is connected
            reader.read_line(&mut message).expect("Cannot read new line");
            println!("message received: {}", message);
            message.clear();
        }
    }
    Err(e) => {
        println!("Fail: {:?}", e)
    }
}

Here's my Kotlin client:

Socket("192.168.134.138", 52001).use { client ->
    client.getOutputStream().use { out ->
        out.write("test\n".toByteArray())
    }
}
while(true) {
    Thread.sleep(15_000)
}

The client send the following line: test\n and it ends with a linebreak for the server to read.

The intended behaviours would be that on the server side it prints message received: test and then the server waits at the read_line() instruction for the next line

It works because I receive the test but the read_line() method does not seem to block nor wait for another message. So it creates an infinite loop. So in the terminal I'm getting:

New connection from 192.168.134.123:7869    
message received: test
message received:
message received:
message received:
message received:

Process finished with exit code 130 (interrupted by signal 2: SIGINT)

And I have to stop the program forcefully.

Any ideas?


Solution

  • To detect the end of the stream, you need to check if read_line() returned Ok(0):

    From the docs:

    If this function returns Ok(0), the stream has reached EOF.

    loop { // Entering the loop when a client is connected
        let mut message = String::new();
        if reader.read_line(&mut message).expect("Cannot read new line") == 0 {
            break;
        }
        println!("message received: {}", message);
    }
    

    Another way option is to use BufReader::lines() iterator:

    for line in reader.lines() {
        let message = line.expect("Cannot read new line");
        println!("message received: {}", message);
    }
    

    This approach is a bit inefficient as it allocates a new String on every iteration. For best performance, you should allocate a single String and reuse it like @BlackBeans pointed out in a comment:

    let mut message = String::new();
    loop { // Entering the loop when a client is connected
        message.clear();
        if reader.read_line(&mut message).expect("Cannot read new line") == 0 {
            break;
        }
        println!("message received: {}", message);
    }