tidy code for asynchronous IO tidy code for asynchronous IO c c

tidy code for asynchronous IO


I suggest take a look on: http://www.kegel.com/c10k.html, second take a look on existing libraries like libevent, Boost.Asio that already do the job and see how they work.

The point is that the approach may be different for each type of system call:

  • select is simple reactor
  • epoll have both edge or level triggered interface that require different approach
  • iocp is proactor require other approach

Suggestion: use good existing library like Boost.Asio for C++ or libevent for C.

EDIT: This is how ASIO handles this

class connection {   boost::asio:ip::tcp::socket socket_;public:   void run()   {         // for variable length chunks         async_read_until(socket_,resizable_buffer,'\n',               boost::bind(&run::on_line_recieved,this,errorplacehplder);         // or constant length chunks         async_read(socket_,buffer(some_buf,buf_size),               boost::bind(&run::on_line_recieved,this,errorplacehplder);   }   void on_line_recieved(error e)   {        // handle it        run();   }};

Because ASIO works as proactor it notifies you when operation is complete andhandles EWOULDBLOCK internally.

If you word as reactor you may simulate this behavior:

 class conn {    // Application logic    void run() {       read_chunk(&conn::on_chunk_read,size);    }    void on_chunk_read() {         /* do something;*/    }    // Proactor wrappers    void read_chunk(void (conn::*callback),int size, int start_point=0) {       read(socket,buffer+start,size)       if( complete )          (this->*callback()       else {          this -> tmp_size-=size-read;          this -> tmp_start=start+read;          this -> tmp_callback=callback          your_event_library_register_op_on_readable(callback,socket,this);       }    }    void callback()    {       read_chunk(tmp_callback,tmp_size,tmp_start);    } }

Something like that.


State machines are one nice approach. It's a bit of complexity up front that'll save you headaches in the future, where the future starts really, really soon. ;-)

Another method is to use threads and do blocking I/O on a single fd in each thread. The trade-off here is that you make I/O simple but may introduce complexity in synchronization.


Great design pattern "coroutine" exists to solve this problem.

It's the best of both worlds: tidy code, exactly like synchronous io flow and great performance without context switching, like async io gives. Coroutine looks inside like an odinary synchronous thread, with single instruction pointer. But many coroutines can run within one OS thread (so-called "cooperative multitasking").

Example coroutine code:

void do_some_io() {   blocking_read(something,sizeof(something));   blocking_read(something_else,sizeof(something_else));   blocking_write(something,sizeof(something));}

Looks like synchronous code, but in fact control flow use another way, like this:

void do_some_io() {   // return control to network io scheduler, to handle another coroutine   blocking_read(something,sizeof(something));    // when "something" is read, scheduler fill given buffer and resume this coroutine    // return control to network io scheduler, to handle another coroutine   CoroSleep( 1000 );   // scheduler create async timer and when it fires, scheduler pass control to this coroutine    ...   // and so on 

So single threaded scheduler control many coroutines with user-defined code and tidy synchronous-like calls to io.

C++ coroutines implementation example is "boost.coroutine" (actually not a part of boost :)http://www.crystalclearsoftware.com/soc/coroutine/This library fully implements coroutine mechanics and can use boost.asio as scheduler and async io layer.