Boost.Asio + Coroutine
まもなくBoost 1.35 RC1リリースということで、Boost.Asioを弄ってみました。
Boost.Asioの非同期メカニズムはよくできているんですが、処理待ちのたびにコールバックするためどうしても処理の流れが分かりにくくなります。
そこで、Hamigaki.Coroutineを使って無理やり処理順に並べてみました。
お題はBoost本2ndのaddmul_server3.cppです。
http://www.kmonos.net/pub/BoostBook/
#include <hamigaki/coroutine/coroutine.hpp> #include <boost/asio.hpp> #include <boost/bind.hpp> namespace coro = hamigaki::coroutines; namespace asio = boost::asio; namespace ip = boost::asio::ip; namespace sys = boost::system; class add_session { friend class add_server; explicit add_session(asio::io_service& io) : socket_(io) { start_ = start_type(boost::bind(&add_session::start_impl, this, _1)); } void start() { start_(); } typedef coro::coroutine<bool(void)> start_type; void next(const sys::error_code& e, size_t size) { if (!start_()) delete this; } bool start_impl(start_type::self& self) { read_until(self, socket_, buf_, '\n'); std::iostream s(&buf_); int x, y; s >> x >> y; s << x+y << std::endl; write(self, socket_, buf_); return false; } void read_until(start_type::self& self, ip::tcp::socket& socket, asio::streambuf& buf, char c) { asio::async_read_until( socket, buf, c, boost::bind(&add_session::next, this, _1, _2) ); self.yield(true); } void write(start_type::self& self, ip::tcp::socket& socket, asio::streambuf& buf) { asio::async_write( socket, buf, boost::bind(&add_session::next, this, _1, _2) ); self.yield(true); } ip::tcp::socket socket_; asio::streambuf buf_; start_type start_; }; class add_server { public: explicit add_server(asio::io_service& io) : io_(io), acc_(io_, ip::tcp::endpoint(ip::tcp::v4(), 10101)) { start(); } private: asio::io_service& io_; ip::tcp::acceptor acc_; add_session* cur_session_; void start() { cur_session_ = new add_session(io_); acc_.async_accept( cur_session_->socket_, boost::bind(&add_server::accept_ok, this, _1) ); } void accept_ok(const sys::error_code& e) { if (e) { delete cur_session_; return; } cur_session_->start(); start(); } }; int main() { asio::io_service io; add_server ads(io); io.run(); }
async_read_until()とasync_write()の呼び出したらyield()して呼び出し元に戻ります。
コールバック関数はnext()に設定していて、ここで再度コルーチンの処理を続行するわけです。
「delete this」はstart_impl()の最後で呼びたいところですが、コルーチンの実行中にコルーチンのコンテキストを破棄することは出来ないので、コルーチンから戻ってから削除するようにしています。
一発で動いたのでちょっとびっくりです。
やっぱり処理順に並んだほうが読みやすいですね。