Reading from a TcpStream with Read::read_to_string hangs until the connection is closed by the remote end

14,128

Check out the docs for Read::read_to_string, emphasis mine:

Read all bytes until EOF in this source, placing them into buf.

Likewise for Read::read_to_end:

Read all bytes until EOF in this source, placing them into buf.

Notably, you aren't reading 512 bytes in the first example, you are pre-allocating 512 bytes of space and then reading every byte until the socket closes - 4 minutes later.

It sounds like you want to use BufRead::read_line:

Read all bytes until a newline byte (the 0xA byte) is reached, and append them to the provided buffer.

This function will continue to read (and buffer) bytes from the underlying stream until the newline delimiter (the 0xA byte) or EOF is found. Once found, all bytes up to, and including, the delimiter (if found) will be appended to buf.

You can also any other technique that will read a fixed amount of data before returning. One such example would be Read::take, which will cap the total number of bytes you can read at a time.

Share:
14,128
Admin
Author by

Admin

Updated on July 23, 2022

Comments

  • Admin
    Admin almost 2 years

    I'm attempting to implement the Haskell IRC bot tutorial in Rust and am having some difficulty reading what the server sends me after connecting. What seems to happen is that I connect, read ~5 KB from the server, and then roughly 240 seconds later everything is dumped at once instead of being read line-by-line. The connection is closed by a ping timeout, which should happen eventually, since I don't yet have a ping-pong function to reply with.

    Here's what I have so far:

    use std::io::{Read, Write};
    use std::net::TcpStream;
    
    fn main() {
        let mut stream = TcpStream::connect("irc.freenode.org:6667").unwrap();
    
        let _ = stream.write(b"NICK G-SERUFU\r\n");
        let _ = stream.write(b"USER G-SERUFU 0 * :brobot\r\n");
        let _ = stream.write(b"JOIN #tutbot-testing\r\n");
    
        let mut line = String::with_capacity(512);
        loop {
            let result = stream.read_to_string(&mut line);
            match result {
                Ok(n) => println!("Received {} bytes", n),
                _ => {}
            }
            line.clear();
        }
    }
    

    When I modify the loop a to use an array instead of a string, I immediately get the output I expect:

    let mut line;
    loop {
        line = [0; 512];
        let result = stream.read(&mut line);
        match result {
            Ok(n) => println!("Received {} bytes",n),
            _ => {},
        }
    }
    

    My conclusion is that stream.read_to_string(&mut line) is somehow the culprit. Why might that be the case? Is there something obvious that I'm overlooking?

    To be more specific, in the first case the output appears after the ping timeout, upon which the following is printed:

    //Around 4 minutes elapse before anything is printed to console
    Received 5323 bytes
    Received 0 bytes
    Received 0 bytes
    Received 0 bytes
    //Continues to print "Received 0 bytes" since the connection has closed but I haven't broken out of the infinite loop
    

    In the second case using the array, I receive the correct output almost immediately:

    Received 64 bytes
    Received 51 bytes
    Received 512 bytes
    Received 512 bytes
    Received 350 bytes
    Received 512 bytes
    Received 512 bytes
    ...