There are numerous C async I/O libraries; tevent being the one I'm most familiar with. Yet, tevent has a very wide API, and programs using it inevitably descend into "callback hell". So I wrote ccan/io.
The idea is that each I/O callback returns a "struct io_plan" which says what I/O to do next, and what callback to call. Examples are "io_read(buf, len, next, next_arg)" to read a fixed number of bytes, and "io_read_partial(buf, lenp, next, next_arg)" to perform a single read. You could also write your own, such as pettycoin's "io_read_packet()" which read a length then allocated and read in the rest of the packet.
This should enable a convenient debug mode: you turn each io_read() etc. into synchronous operations and now you have a nice callchain showing what happened to a file descriptor. In practice, however, debug was painful to use and a frequent source of bugs inside ccan/io, so I never used it for debugging.
And I became less happy when I used it in anger for pettycoin, but at some point you've got to stop procrastinating and start producing code, so I left it alone.
Now I've revisited it. 820 insertions(+), 1042 deletions(-) and the code is significantly less hairy, and the API a little simpler. In particular, writing the normal "read-then-write" loops is still very nice, while doing full duplex I/O is possible, but more complex. Let's see if I'm still happy once I've merged it into pettycoin...