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()の最後で呼びたいところですが、コルーチンの実行中にコルーチンのコンテキストを破棄することは出来ないので、コルーチンから戻ってから削除するようにしています。


一発で動いたのでちょっとびっくりです。
やっぱり処理順に並んだほうが読みやすいですね。