You can try ioctl
. FIONREAD tells you how many bytes are immediately readable. If this is the same as the buffer size (which you might be able to retrieve and/or set with another icotl call), then the buffer is full. Likewise, if you can write as many bytes as the size of the output buffer, then the output buffer is empty.
I don't how widely supported FIONREAD, FIONWRITE, and SIOCGIFBUFS (or equivalents) are. I'm not sure I've ever used any of them, although I've a sneaky feeling I've used similar functionality on Symbian for some reason or other.
Whether the call needs kernel mode to compute this is platform-specific. Vaguely trying to avoid system calls is not a valid optimisation technique.
A basic BSD-style sockets interface doesn't say anything much about read and write buffers. When does it matter whether the send buffer is empty? It certainly doesn't mean that all the data has been received at the other endpoint of the socket - it could be sitting in some router somewhere. Likewise, "your" read buffer being full doesn't guarantee that a write at the other end will block.
Generally speaking, you just read/write as much as you can and let the sockets layer handle the complexity. If you're seeing a lot of I/O completed with tiny sizes then maybe there's some performance problem. But remember that a stream socket will send/receive a packet at a time, containing a block of data. Unless TCP_NODELAY is set, it's not as though bytes are arriving by ones at the NIC, and you might end up making one read call per byte. They're arriving in packets, so most likely will become readable all at once, perhaps 1k-ish at a time. You're unlikely to be able to speed things up by holding off reading until there's a lot to read. In fact you might make it worse, because by the time your endpoint's read buffer is full, there's a risk that incoming data is being discarded because there's nowhere to store it, resulting in delays and re-sends.