2011年11月10日木曜日

BOOST::ASIOを使ってみる(2)

ネットワークでパケットのやりとりを行う際に同期処理(接続があるまで待っている)だけで行うと、単体スレッドでは実用的ではありません。その場合、マルチスレッド処理を使って行いますが、パフォーマンスが落ちる場合があります。
Boost::Asioでは、非同期処理を推奨しており、必要なときにイベントを発生させて処理させることで、単体スレッドでもパケットのやり取りができるようになるようです。
Boost::AsioではProactorデザインパターンを使っているようです。

http://www.boost.org/doc/libs/1_47_0/doc/html/boost_asio/overview/core/async.html

どのようなデザインパターンかは勉強不足のため分りません(汗)が、キューに(イベント)ハンドラーを入れていき、イベントが完了したら完了(イベント)ハンドラーを呼ぶらしいです。

ソースコードは以下です。

ServerModule.hpp

#pragma once

#ifndef __ASIO_SERVER_MODULE__
#define __ASIO_SERVER_MODULE__

#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>

namespace thorny_road{

using namespace boost::asio ;

class ServerModule : public boost::noncopyable
{
private:
    io_service& io ;
    ip::tcp::acceptor accept ;
    boost::asio::streambuf buf ;
    ip::tcp::socket socket ;
public:
   
    ServerModule(io_service& io, const short port)
        :io(io),accept(io,ip::tcp::endpoint(ip::tcp::v4(),port)),socket(io)
    {
        start_accept() ;
    }

    void start_accept()
    {
        // Enqueue Accept Handler
        accept.async_accept(
            socket,
            boost::bind(&ServerModule::accept_ok,this,_1) ) ;
    }

    void accept_ok(const boost::system::error_code e)
    {
        // Enqueue Read Handler
        async_read_until(
            socket,
            buf,
            '\n',
            boost::bind(&ServerModule::read_ok,this,_1) ) ;
    }

    void read_ok(const boost::system::error_code e)
    {
        std::iostream ios(&buf) ;

        std::string tmp ;
        ios >> tmp ; // get input stream
        ios << tmp <<std::endl ; // retrun as it is

        // Enqueue Read Handler
        async_write(
            socket,
            buf,
            boost::bind(&ServerModule::write_ok,this,_1) ) ;
    }

    void write_ok(const boost::system::error_code e)
    {
        // Finish
    }


} ;

} // namespace snb
#endif //__ASIO_SERVER_MODULE__

Main.cpp
#include "stdafx.h"
#include "ServerModule.hpp"

#include <boost/asio.hpp>

using namespace thorny_road ;

int _tmain(int argc, _TCHAR* argv[])
{
    boost::asio::io_service io ;

    ServerModule svr(io,2085) ; // Start Server Module

    io.run() ;

    return 0;
}


非同期処理ではSocketを使って相互処理を行います。同期処理ではaccept.accept(...)を使いましたが、非同期処理ではaccept.async_accept(...)を使います。第一引数がSocketで、第二引数が完了ハンドラーとなります。ですので、この場合、接続が完了するとaccept_ok(...)が呼ばれます。boost::bind(...)は関数オブジェクトを簡単に使うためにBoostで用意されているものです。DELPHIの「.... of object」みたいなものかな?
async_read_until(...)は指定した完了条件になるまで受信する非同期処理関数です。この場合、第三引数に改行コードを設定しているので、改行を受信と完了ハンドラーが呼ばれます。
async_write(...)は指定したストリームに入っている情報を送信します。
一般にBoost::Asioではboost::asio::streambufを使ってバッファ処理をするのが良いようです。勉強不足(汗)で利点はよく分りません・・・。
メイン処理(_tmain)ではio.run()を追加しています。これは非同期処理ではハンドラーをキューに入れると、待機せずに処理が戻ってくるためです。io.run()で「ハンドラーが無くなる」までループします。
前回同様telnetから接続すると、入力したものがそのままエコーバックされると思います。

現状のままだと、一度やり取りをするとプログラムが終わってしまいます。そこで、write_okの中でstart_accept()を呼ぶようにします。こうすることで再び接続待ちになります。

    void write_ok(const boost::system::error_code e)
    {
        // Finish
        start_accept() ;
    }


ただし、この場合、io.run()が無限にループするようになります。

次はパケットのやり取り部分を分けたいと思います。

ソースコードは自由に使用ください。ただし、問題が起きても責任はとりません。

0 件のコメント:

コメントを投稿