You cannot do proper asynchronous (non-blocking) i/o using the standard
streambuffers. basic_streambuf::underflow() returns a signed integer
representing either the character at the start of the "pending
sequence" (the front of the buffered characters available) following the
read operation, or traits_type::eof() (normally -1) if end-of-file or an
error has arisen. As far as I can tell, underflow() returning 0 means
that a character of value '\0' has been received and is at the front of
the buffer, which would conflict with the fact that after underflow()
gptr() does not in fact point to that (non-existent) '\0' character.
The call to uflow() which invokes underflow() (and which does advance
gptr()) will therefore cause the character in fact at the gptr()
position to be lost.
In short, the standard streams (and streambuffers) expect a read
operation to block until the read request has been satisfied unless
either end-of-file has been reached or an error has arisen.
You could fudge it by ensuring that underflow() will never in
fact return with nothing added to the buffer. You could do
this by extracting a single character at a time from the buffer,
looping on basic_streambuf::in_avail() and when the buffer is empty
testing the underlying file descriptor for readiness with poll() or
select() in your asynchronous event loop. One of the problems with
this when using linux (if that is your OS) is that select() and poll()
on linux are not POSIX compliant and can spuriously report a file
descriptor as ready, so this arrangement could still block when you do
not want it to or (if the file descriptor is set non-blocking) cause
EAGAIN or EWOULDBLOCK to put the streambuffer in an error state when
there is in fact no error.
Chris