5

I have a std::net::TcpStream. I want to determine if there is data available to be read without actually reading it yet.

The only relevant API I can find on TcpStream itself is read which

does not provide any guarantees about whether it blocks waiting for data

which does not sound encouraging for this problem.

A related question seems to drop down to file descriptors and read(2) to force an nonblocking read. However I cannot figure out how to use read(2) to peek at an fd without actually reading it.

I suppose this is a job for select(2), but constructing the fd_sets for the C arguments seems rather hairy. There certainly is not a Rust type for that, and it's not immediately clear how I would invent one.

Community
  • 1
  • 1
Drew
  • 8,675
  • 6
  • 43
  • 41
  • If that's what the documentation says it's too modest. It doesn't have any choice but to block if the underlying socket is in blocking mode and there is no data. And surely the API provides access to FIONREAD somehow? – user207421 Mar 05 '15 at 07:55
  • @EJP: but with what is written, it reserves the right to simply return no data. Whether it *will* or not may merely be a matter of present implementation. – Chris Morgan Mar 05 '15 at 07:57
  • @ChrisMorgan I'd like someone to point us to what actually is written. – user207421 Mar 05 '15 at 08:22
  • @EJP: what? The documentation, or the implementation? It shouldn’t be too hard to trace through the code if you’re interested. – Chris Morgan Mar 05 '15 at 12:55

1 Answers1

1

I suppose this is a job for select(2), but constructing the fd_sets for the C arguments seems rather hairy.

I suppose poll(2) should be more convenient. For example:

#![feature(std_misc, net, libc, os, io)]

extern crate libc;
use libc::{c_int, c_uint, c_short};
use std::thread::spawn;
use std::net::{TcpListener, TcpStream};
use std::os;
use std::io::Read;
use std::os::unix::AsRawFd;

#[repr(C)]
struct pollfd {
    fd: c_int,
    events: c_short,
    revents: c_short,
}

extern { fn poll(fds: *mut pollfd, nfds: c_uint, timeout: c_int) -> c_int; }

const POLLIN: c_short = 1;

fn handle_client(mut stream: TcpStream) {
    let mut fdset = pollfd { fd: stream.as_raw_fd(), events: POLLIN, revents: 0, };
    loop {
        match unsafe { poll(&mut fdset as *mut _, 1, -1) } {
            ret if ret < 0 => panic!("poll error: {}", os::last_os_error()),
            ret if ret > 0 && fdset.events == fdset.revents => {
                let mut byte: &mut [u8] = &mut [0];
                match stream.read(&mut byte).unwrap() {
                    0 => break,
                    1 => println!("A byte read: {}", byte[0]),
                    _ => unreachable!(),
                }
            },
            _ => break,
        }
    }
}

fn main() {
    let listener = TcpListener::bind("127.0.0.1:9999").unwrap();
    for stream in listener.incoming() {
        match stream {
            Ok(stream) => { spawn(move || handle_client(stream)); },
            Err(e) => panic!("connection error: {}", e),
        }
    }
}
swizard
  • 2,551
  • 1
  • 18
  • 26
  • If I'm not mistaken, that line should read `ret if ret > 0 && fdset.events & fdset.revents != 0 =>` – Drew Mar 06 '15 at 07:37
  • You are right, that would be more accurate. However, I don't think that `poll` can return positive value in case when no given events was tiggered. – swizard Mar 06 '15 at 11:40
  • It can return nonnegative values (e.g., zero) however. – Drew Mar 08 '15 at 05:48
  • Zero values should be processed by `_` match as well. It executes `break` in my example, but you can use `continue` in case you want process `EINTR` errors. – swizard Mar 09 '15 at 01:50