Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust persistent TcpStream

Tags:

tcp

sockets

rust

I seem to be struggling with the std::io::TcpStream. I'm actually trying to open a TCP connection with another system but the below code emulates the problem exactly.

I have a Tcp server that simply writes "Hello World" to the TcpStream upon opening and then loops to keep the connection open.

fn main() {
  let listener = io::TcpListener::bind("127.0.0.1", 8080);
  let mut acceptor = listener.listen();
  for stream in acceptor.incoming() {

    match stream {
      Err(_) => { /* connection failed */ }
      Ok(stream) => spawn(proc() {
        handle(stream);
      })
    }
  }
  drop(acceptor);
}

fn handle(mut stream: io::TcpStream) {
  stream.write(b"Hello Connection");
  loop {}
}

All the client does is attempt to read a single byte from the connection and print it.

fn main() {
    let mut socket = io::TcpStream::connect("127.0.0.1", 8080).unwrap();
    loop {
      match socket.read_byte() {
        Ok(i) => print!("{}", i),
        Err(e) => {
          println!("Error: {}", e);
          break
        }
      }
    }
}

Now the problem is my client remains blocked on the read until I kill the server or close the TCP connection. This is not what I want, I need to open a TCP connection for a very long time and send messages back and forth between client and server. What am I misunderstanding here? I have the exact same problem with the real system i'm communicating with - I only become unblocked once I kill the connection.

like image 260
Upio Avatar asked Sep 03 '14 06:09

Upio


1 Answers

Unfortunately, Rust does not have any facility for asynchronous I/O now. There are some attempts to rectify the situation, but they are far from complete yet. That is, there is a desire to make truly asynchronous I/O possible (proposals include selecting over I/O sources and channels at the same time, which would allow waking tasks which are blocked inside an I/O operation via an event over a channel, though it is not clear how this should be implemented on all supported platforms), but there's still a lot to do and there's nothing really usable now, as far as I'm aware.

You can emulate this to some extent with timeouts, however. This is far from the best solution, but it works. It could look like this (simplified example from my code base):

let mut socket = UdpSocket::bind(address).unwrap();

let mut buf = [0u8, ..MAX_BUF_LEN];
loop {
    socket.set_read_timeout(Some(5000));
    match socket.recv_from(buf) {
        Ok((amt, src)) => { /* handle successful read */ }
        Err(ref e) if e.kind == TimedOut => {}  // continue
        Err(e) => fail!("error receiving data: {}", e)  // bail out
    }

    // do other work, check exit flags, for example
}

Here recv_from will return IoError with kind set to TimedOut if there is no data available on the socket during 5 seconds inside recv_from call. You need to reset the timeout before inside each loop iteration since it is more like a "deadline" than a timeout - when it expires, all calls will start to fail with timeout error.

This is definitely not the way it should be done, but Rust currently does not provide anything better. At least it does its work.

Update

There is now an attempt to create an asynchronous event loop and network I/O based on it. It is called mio. It probably can be a good temporary (or even permanent, who knows) solution for asynchronous I/O.

like image 56
Vladimir Matveev Avatar answered Oct 10 '22 23:10

Vladimir Matveev