2011年11月12日土曜日

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

今回はio_service.run()の挙動を簡単に確認してみたいと思います。run()はキューに入っているハンドラーが無くなるまでループしています。前回のソースコードでは接続ハンドラーaccept_ok(...)の処理中に次の接続ハンドラーを登録していますので、常にキューにハンドラーが存在している状態になっています。

ちょっとした実験を兼ねて、"bye"と入力すると、接続ハンドラーが破棄されるようなものを作ってみます。

デリゲートのような関数オブジェクトを簡単に実装できるものとしてboost::functionがあります。使い方は簡単でboost::function<戻り値型 (引数型)> というように型を宣言します。

今回は単純な戻り値・引数が無い関数オブジェクトを使うので

typedef boost::function<void ()> SimpleProcedure ;


という風にしています。

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

ServerModule.hpp



#pragma once

#ifndef __ASIO_SERVER_MODULE__
#define __ASIO_SERVER_MODULE__

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

namespace thorny_road{

using namespace boost::asio ;

// For calling event function handler easily.
typedef boost::function<void ()> SimpleProcedure ;

class ServerSession : private boost::noncopyable
{
private:
    io_service& io ;
    ip::tcp::socket socket ;
    boost::asio::streambuf buf ;
    SimpleProcedure acc_stop ;
public:
    ServerSession(io_service& io,SimpleProcedure acc_stop)
        : io(io),socket(io),acc_stop(acc_stop) {}

    ~ServerSession() {}

    void start()
    {
        // Enqueue Read Handler
        async_read_until(
            socket,
            buf,
            '\n',
            boost::bind(&ServerSession::read_ok,this,_1) ) ;
    }

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

            std::string tmp ;
            ios >> tmp ; // get input stream
            std::cout << tmp ;
            if (tmp == "end") { delete this; return; }
            else if (tmp == "bye")
            {
                acc_stop() ;
                delete this;
                return;
            }

            ios << tmp <<std::endl ; // retrun as it is

            // Enqueue Write Handler
            async_write(
                socket,
                buf,
                boost::bind(&ServerSession::write_ok,this,_1) ) ;
        }
        else if (e == boost::asio::error::operation_aborted)
        {
            // abort
            std::cout << "connection abort" ;
            delete this;
            return ;
        }
        else
        {
            std::cout << "error" ;
            delete this;
            return ;
        }
    }

    void write_ok(const boost::system::error_code e)
    {
        if (!e)
        {
            start() ; // Restart
        }
        else if (e == boost::asio::error::operation_aborted)
        {
            // abort
            std::cout << "connection abort" ;
            delete this;
            return ;
        }
        else
        {
            std::cout << "error" ;
            delete this;
            return ;
        }
    }

public:
    ip::tcp::socket& getSocket()
    {
        return (socket) ;
    }

} ;

class ServerModule : private boost::noncopyable
{
private:
    io_service& io ;   
    ip::tcp::acceptor accept ;
    ServerSession* session ;
public:
   
    ServerModule(io_service& io, const short port)
        :io(io),accept(io,ip::tcp::endpoint(ip::tcp::v4(),port))
    {
        start_accept() ;
    }

    void start_accept()
    {
       
        session = new ServerSession(io,boost::bind(&ServerModule::stop_accept,this)) ;
        // Enqueue Accept Handler
        accept.async_accept(
            session->getSocket(),
            boost::bind(&ServerModule::accept_ok,this,_1) ) ;
    }

    // Accept service will be closed. Then IOServce throw operation_aborted.
    // After queue get be empty, the io.run() will stop.
    void stop_accept()
    {
        accept.close() ;
    }

    void accept_ok(const boost::system::error_code e)
    {
        if (!e)
        {
            session->start() ;
            start_accept() ;
        }
        else if (e == boost::asio::error::operation_aborted)
        {
            // abort
            std::cout << "connection abort" ;
            delete session ;
            session = NULL ;
            return ;
        }
        else
        {
            // error
            std::cout << "error" ;
            delete session ;
            session = NULL ;
            return ;
        }
    }

} ;

} // namespace thorny_road
#endif //__ASIO_SERVER_MODULE__

ServerSessionクラスのコンストラクタでacc_stopとして関数オブジェクトを受け取るようにしています。"bye"と入力されると、ServerModule::stop_accept()が呼ばれます。ServerModule::stop_accept()ではaccept.close()が実行され、キューに入っている接続用のハンドラーが破棄されます。

ハンドラーが破棄されると、破棄されたハンドラー関数にboost::asio::error::operation_abortedが入ってきます。今回、分りやすいように標準出力に"abort"と出力するようにしています。

前回同様、telnetでアクセスし、byeと入力するとアプリケーションが終了すると思います。これは接続ハンドラーが破棄され、Sessionの中でも新たにハンドラーがキューに代入されないからです。

たとえば、telnetを2つ起動し、両方から接続していると、片方でbyeと入力してもアプリケーションは終了しません。これはもう片方のtelnetのハンドラーが残っているからです。

キューにハンドラーが無くなってもio.run()が終了しないようにio_service::workクラスが用意されています。
今後、試してみたいと思っています。

単体のスレッドで、複数のクライアントからアクセスできるのは素晴らしいことですが、やはり逐次処理のため、どれかが処理している間は他の処理がブロックされてしまいます。この問題を回避するためにはマルチスレッドを使う必要がありそうです。


ソースコードは自由にご使用ください。ただし問題が起きても責任はとれません。また、ソースコードに対する著作権は放棄していません。

0 件のコメント:

コメントを投稿