2011年11月26日土曜日

DELPHIでInterfaceを使ってみる(3)

DELPHIのInterfaceはクラスと同じように継承・派生することが可能です。

たとえば、エラ呼吸かどうかを確認するインターフェイスをITypeからの派生Interfaceとして作ります。
  IIsBranchial = interface(IType)
    function IsBranchial() : Boolean ; // True or False
  end;
前回までのクラスをIIsBranchialから派生するようにします。
  THuman = class(TNonRefInterfacedObject,IIsBranchial)
  public
    function GetTypeName() : string ; // human
    function IsBranchial() : Boolean ; // false
  end;

  TAnimal = class(TNonRefInterfacedObject,IIsBranchial)
  public
    function GetTypeName() : string ; // animal
    function IsBranchial() : Boolean ; // false
  end;

  TFish = class(TNonRefInterfacedObject,IIsBranchial)
  public
    function GetTypeName() : string ; // fish
    function IsBranchial() : Boolean ; // true
  end;
この場合、各クラスはITypeとIIsBranchial両方のInterfaceを実装している必要があります。実装が足らない場合、コンパイルエラーとなります。
また、IIsBranchialはITypeからの派生とはいえ、明示的に継承していないクラスからのキャストはできません。たとえば、上記THumanからITypeへはキャストできません。キャストが必要な場合は明示的に継承します。


THuman = class(TNonRefInterfacedObject,IIsBranchial,IType)


DELPHIにはチェック付きキャストとしてas演算子が用意されています。これはキャストが不正な場合、例外投げられるというものです。as演算子を用いてInterfaceにキャストする場合、このままでは実行時例外が投げられてしまいます。

キャストできるようにするにはInterfaceの宣言時にGUIDを追加してあげます。今回の場合、以下のようになります。
  IType = interface(IUnKnown)
  ['{5CDFEE96-1683-4A40-A490-34D064DCAA18}']
    function GetTypeName() : string ;
  end;

  IIsBranchial = interface(IType)
  ['{B034BE0E-4087-4C45-B78A-CCF6FB7BF9B5}']
    function IsBranchial() : Boolean ;
  end;
DELPHIではCtrl + Shift + Gで簡単に追加することができます。



  • インターフェースでの as 演算子の使い方





  • 次回ももう少しInterfaceを使ってみたいと思います。


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

    2011年11月25日金曜日

    DELPHIでOverloadを使って簡単なCASTを作ってみる(2)


    前回でImplicitを使ってみました。結局Implicit演算子関数のデフォルトで呼ばれる関数がどのように決まるか分りませんでした・・・(知っている方、教えてください(_ _))
    なので、使われる型に関しては実装しておくのが一番ではないしょうか?
    また、引数のオーバーロードも戻りのオーバーロード(のようなもの)もコンパイル時点で型のチェックがされ、実装してしまうようなので、コンパイル時点で推測できるようしないといけません。

    たとえばTCastの場合次のような使い方はコンパイルエラーとなります。

    Format('Integer:%d',[TCast('250')]) ; //<- "E2250 There is no overloaded version of 'Format' that can be called with these arguments"


    これはコンパイル時にはTCast(...)が何を返すのか分らないからです。第一引数には%dを含んでいますが、この時点ではただの文字列です。明示的なキャストをすれば、型の推測が可能になりコンパイルできます。

    Format('Integer:%d',[Integer(TCast('250'))]) ; //<- OK


    TCastのソースコードを以下に示します。






    unit Thorny.NumericCast;
    
    interface
    uses SysUtils;
    
    type
      TCast = record
      private
        pS : string ;
    
      public
        class operator Explicit(const v : string) : TCast ; inline ;
    
      public
        class operator Implicit(const v : TCast) : Integer ; inline ;
        class operator Implicit(const v : TCast) : Int64 ; inline ;
        class operator Implicit(const v : TCast) : Extended ; inline  ;
        class operator Implicit(const v : TCast) : Double ; inline  ;
        class operator Implicit(const v : TCast) : Single ; inline  ;
    
        function TryTo(var v : Integer) : Boolean ; overload ;
        function TryTo(var v : Int64) : Boolean ; overload ;
        function TryTo(var v : Extended) : Boolean ; overload ;
        function TryTo(var v : Double) : Boolean ; overload ;
        function TryTo(var v : Single) : Boolean ; overload ;
      end;
    
    
    implementation
    
    
    { TCast }
    
    class operator TCast.Explicit(const v: string): TCast;
    begin
      result.pS := v ;
    end;
    
    class operator TCast.Implicit(const v: TCast): Extended;
    begin
      result := StrToFloat(v.pS) ;
    end;
    
    class operator TCast.Implicit(const v: TCast): Integer;
    begin
      result := StrToInt(v.pS) ;
    end;
    
    class operator TCast.Implicit(const v: TCast): Double;
    begin
      result := StrToFloat(v.pS) ;
    end;
    
    
    class operator TCast.Implicit(const v: TCast): Single;
    begin
      result := StrToFloat(v.pS) ;
    end;
    
    class operator TCast.Implicit(const v: TCast): Int64;
    begin
      result := StrToInt64(v.pS) ;
    end;
    
    function TCast.TryTo(var v: Single): Boolean;
    begin
      result := TryStrToFloat(pS,v) ;
    end;
    
    function TCast.TryTo(var v: Int64): Boolean;
    begin
      result := TryStrToInt64(pS,v) ;
    end;
    
    function TCast.TryTo(var v: Integer): Boolean;
    begin
      result := TryStrToInt(pS,v) ;
    end;
    
    function TCast.TryTo(var v: Extended): Boolean;
    begin
      result := TryStrToFloat(pS,v) ;
    end;
    
    function TCast.TryTo(var v: Double): Boolean;
    begin
      result := TryStrToFloat(pS,v) ;
    end;
    
    end.
    
    
    TryTo関数を追加してみました。これは見てわかるようにTryStrTo...関数を呼んでいるだけです(汗)。 varを付けた引数の場合、型が一致している必要があるようです。共変性などあるといいと思うのですが。

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

    DELPHIでOverloadを使って簡単なCASTを作ってみる(1)

    DELPHI2007から、演算子のオーバーロードができるようになりました。

    演算子のオーバーロードについて


    演算子のオーバーロードと構造体を使うことで新しい型などを作ることができるようになります。今回は演算子のExplicitImplicitを使って文字列から数字にキャストする構造体と作ってみたいと思います。行う処理は変換関数をラップしているだけですが、見た目がカッコ良くなります。

    通常、文字列からIntegerに変換する場合、

    intg := StrToInt('218') ;

    と書きますが、これが

    intg := TCast('218') ;

    と書けるように工夫してみます。また、演算子のオーバーロードを利用することで、Double型にも同じ形で使えるようにしたいと思います。

    doub := TCast('3.14') ;

    Explicitは明示的なキャストと言われ、所謂"()"を使ったキャストです。Implicitは暗黙的なキャストで代入処理で暗黙的に行われるキャストです。

    TCastではExplicit演算子関数をあたかも(C言語でいう)コンストラクタに見えるよう使っています。

    TCastを以下に示します。

      TCast = record
      private
        pS : string ;
    
      public
        class operator Explicit(const v : string) : TCast ; inline ;
    
      public
        class operator Implicit(const v : TCast) : Integer ; inline ;
        class operator Implicit(const v : TCast) : Extended ; inline  ;
    
      end;
    
    class operator TCast.Explicit(const v: string): TCast;
    begin
      result.pS := v ;
    end;
    
    class operator TCast.Implicit(const v: TCast): Integer;
    begin
      inherited;
      result := StrToInt(v.pS) ;
    end;
    
    class operator TCast.Implicit(const v: TCast): Extended;
    begin
      result := StrToFloat(v.pS) ;
    end;
    
    Implicit":="の演算子関数で、複数宣言可能です(Explicitも複数宣言できます)

    A := TFoo.... (A:変数型)

    の場合は
    class operator TFoo.Implicit(const v: TFoo): A;

    と書き、

    TFoo... := A (A:変数型)

    の場合は

    class operator TFoo.Implicit(const v: A): TFoo;

    と書きます。

    これを使って戻り値のオーバーロードのようなものを実現します。引数のオーバーロードと同様、型は一致していないとデフォルトの関数が呼ばれるようなので注意が必要です。

    次回はオーバーロードに関してもう少し考えてみます。

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




    2011年11月14日月曜日

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

    非同期処理であるASIOをマルチスレッド上で動かしてみます。
    その前に前回までのソースコードを少し変更しています。簡単に関数オブジェクトを実装できるboost::format<T>ですが、前回まではSessionのメンバ変数の値として持っていましたが、ServerModuleが先に解放された場合、クリアできなくなるので参照として持つようにしました。その代わりServerModuleのメンバ変数として存在するようにしました。値型と参照やスコープなどを考えないといけないところが私にとってC++が難しく感じるところかもしれません。
    boost::format<T>などの使い方も勉強していきたいと思います。Lamdaもあって便利だと思いますので。

    今回、boost::thread_groupを使いたいと思います。Threadをグループとして扱うことができます。boost::io_service::ioはハンドラーを効率よくスレッドに振り分けるようで、何も考えずにスレッドの中でio.run()を実行します。スレッドを開始するクラスをtemplateを使って少し汎用的にして別ファイルにしました。

    前回少し説明したio_service::workクラスを使っています。今回、マルチスレッドの各スレッドでio.run()を実行してもキューが空だと終了してしまうからです。io_service::workを代入して空ではない状態にします。逆に終了するときはio_service::workを破棄します。

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

    ModuleThread.hpp
    #include <iostream>
    #include <boost/asio.hpp>
    #include <boost/bind.hpp>
    #include <boost/thread.hpp>
    #include <boost/shared_ptr.hpp>
    
    namespace thorny_road{
    
    // T is necessary 'stop', 'constructor(io,short port)'
    template<typename T>
    class ModuleThread : private boost::noncopyable
    {
    private:
        boost::thread_group tg ;
        boost::shared_ptr<T> _module ; // Target Module
        boost::shared_ptr<boost::asio::io_service> io_ptr ; // for creating service io
        boost::asio::io_service& io ; // service IO
        boost::shared_ptr<boost::asio::io_service::work> work;
        short port ;
    public:
        ModuleThread(short Port, int NoThreads)
            : port(Port),
            io_ptr(new boost::asio::io_service()),
            io(*io_ptr),
            work(new boost::asio::io_service::work(io)) // add waiting work in io loop
        {
            io.reset() ;
            // Create some threads. and then run io.
            for (int i = 0 ; i < NoThreads; i++)
                tg.create_thread( boost::bind(&boost::asio::io_service::run, &io) ) ;
    
            // Create module
            _module.reset(new T(io,Port)) ;
        }
    
        // Destructor. reset IO work and waiting by stopping threads
        ~ModuleThread()
        {
            stop() ;
        }
    
        void stop()
        {
            work.reset() ; // destroy work object which is the waiting object.
            tg.join_all() ; // waiting by stopping threads.
    
            _module.reset() ;
        }
    
        T& getModule() { return *_module ; }
        T& getModule() const { return static_cast<const T&>(getModule()); }
    } ;
    
    } // thorny_road
    
    #endif  //__ASIO_MODULE_THREAD__
    

    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>
    #include <boost/shared_ptr.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 ;
        SimpleProcedure stopEvent ;
    public:
        ServerModule(io_service& io, const short port)
            :io(io),accept(io,ip::tcp::endpoint(ip::tcp::v4(),port))
        {
            // Initialize Function Object
            stopEvent = boost::bind(&ServerModule::stop,this) ;
            start_accept() ;
        }
    
        ~ServerModule() {}
    
        void start_accept()
        {
            session = new ServerSession(io,stopEvent) ;
            // 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.close() ;
        }
    private:
    
        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__
    
    Main

    #include "stdafx.h"
    #include "ServerModule.hpp"
    #include "ModuleThread.hpp" // for threading
    
    using namespace thorny_road ;
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        ModuleThread<ServerModule> module(2085,2) ; // create module.
    
        return 0;
    }
    
    ModuleThreadの引数でポート番号とスレッドの数を指定します。"bye"と入力があると、ハンドラーが破棄され、アプリケーションが終了します。

    次回は送信側を勉強してみます。

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

    DELPHIでInterfaceを使ってみる(4)

    前回はInterfaceを継承した場合を見てましたが、今回は実体であるクラスから派生したクラスでどうなるか見てみます。

    前回までのソースコードの抜粋です。
      IType = interface(IUnKnown)
      ['{5CDFEE96-1683-4A40-A490-34D064DCAA18}']
        function GetTypeName() : string ;
      end;
    
      IIsBranchial = interface(IType)
      ['{B034BE0E-4087-4C45-B78A-CCF6FB7BF9B5}']
        function IsBranchial() : Boolean ;
      end;
    
      TNonRefInterfacedObject = class(TObject, IInterface)
      protected
        function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
        function _AddRef: Integer; stdcall;
        function _Release: Integer; stdcall;
      end;
    
      THuman = class(TNonRefInterfacedObject,IIsBranchial,IType)
      public
        function GetTypeName() : string ; // return 'Human'
        function IsBranchial() : Boolean ; // false
      end;
    
    THumanクラスではIType,IIsBranchialの両方明示的に継承しています。ですので、以下のような関数で使うとHumanと出力されます。

    function GetTypeName(intf : IType) : String ;
    begin
      exit(intf.GetTypeName()) ;
    end;
    ...
        human := THuman.Create() ;
        Writeln(GetTypeName(human)) ;
    
    出力
    Human
    
    ではTHumanから派生したTNewHumanではどうなるか試してみました。
      TNewHuman = class(THuman)
    
      end;
    ...
        human := TNewHuman.Create() ;
        Writeln(GetTypeName(human)) ;
    
    出力
    Human
    
    派生元のクラスにおけるITypeインターフェイス実装が呼ばれました。
    Intefaceの継承(IIsBranchial = Interface(IType)...)は場合は明示的に継承していないと呼ばれず、クラスの継承の場合は派生元のクラスで継承していれば呼ばれます。
    その理由としてはvtable,VMTや仮想クラスなど勉強すれば分りそうです。後々の課題とします。また、インタフェースを継承するとその分インスタンスサイズが増えるので使う際には考えて設計する方がいいです。

    次に、TNewHumanで各関数をオーバーライドした場合を考えます。
      THuman = class(TNonRefInterfacedObject,IIsBranchial,IType)
      public
        function GetTypeName() : string ;  virtual ; // return 'Human'
        function IsBranchial() : Boolean ; virtual ; // false
      end;
    
      TNewHuman = class(THuman)
      public
        function GetTypeName() : string ; override ; // return 'NewHuman'
        function IsBranchial() : Boolean ; override ; // false
      end;
    
    出力
    NewHuman
    
    結果としてオーバーライドした関数の結果が返ってきました。Interface関数の実体における実装は仮想関数と同じ扱いだと考えてよさそうです。


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

    DELPHIでInterfaceを使ってみる(2)

    前回ではInterfaceの持つ参照カウンター機能について書きました。他にもIntefaceには「特定のメソッド」を持つオブジェクトをグループ分けする使い方があります。たとえば以下のようなInterfaceを用意します。




  • インターフェースオブジェクトのメモリ管理




  •   IType = interface(IUnKnown)
        function GetTypeName() : string ;
      end;
    
    これを実装したクラスを用意します。
      THuman = class(TInterfacedObject,IType)
        ....
        function GetTypeName() : string ; // get 'Human'
      end ;
    
      TAnimal = class(TInterfacedObject,IType)
        ....
        function GetTypeName() : string ; // get 'Animal'
      end ;
    
      TFish = class(TInterfacedObject,IType)
        ....
        function GetTypeName() : string ; // get 'Fish'
      end ;
    
    これらを利用して次のようなTypeNameを取得する関数を作りたいと考えます。
    function GetTypeName(intf : IType) : String ;
    begin
      exit(intf.GetTypeName()) ;
    end;
    
    GetTypeName(...)関数は引数のintfからGetTypeName()を実行し、TypeNameを取得したいと考えいます。たとえば、以下のようにTHumanのインスタンスオブジェクトを引数に入力すると、'Human'と返すなどです。しかし、ここで問題が分ります。
    human := THuman.Create() ;
    Writeln(GetTypeName(human)) ; // print out 'Human'
    ......
    
    ここで使っているInterfaceは参照カウンタ付きのInterfaceのため、GetTypeName(...)関数の中でhumanオブジェクトは解放されてしまいます。となると、GetTypeName(...)を抜けると、humanは解放されているため、混乱や問題が生じる可能性があります。
    このような場合は参照カウンターを無効にしたInterfaceを使えば解放される心配はありません。

    DELPHIには参照カウンターが無効になるよう実装しているクラスがありませんので、自分で実装します。
    (2011/11/24 参照カウンターが無効になるように実装しているクラスがDELPHIには用意されていました - TSingletonImplementation というクラスでGenerics.Defautsにあります)

    次のような感じでしょうか。
      TNonRefInterfacedObject = class(TObject, IInterface)
      protected
        function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
        function _AddRef: Integer; stdcall;
        function _Release: Integer; stdcall;
      end;
    
    ...
    
    function TNonRefInterfacedObject.QueryInterface(const IID: TGUID;
      out Obj): HResult;
    begin
      if GetInterface(IID, Obj) then
        Result := 0
      else
        Result := E_NOINTERFACE;
    end;
    
    function TNonRefInterfacedObject._AddRef: Integer;
    begin
      exit(-1) ;
    end;
    
    function TNonRefInterfacedObject._Release: Integer;
    begin
      exit(-1) ;
    end;
    

    この場合、_AddRefが呼ばれても_Releaseが呼ばれても参照カウンターは変化しません。

    次ももう少しだけDELPHIのInterfaceを使ってみます。


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

    DELPHIでInterfaceを使ってみる(1)

    (※以下の話はDELPHI2010を用いていることが前提になっています)
    DELPHIIntefaceを実装するための簡単なメモを書いていきたいと思います。InterfaceはもともとCOM(Common Object Model)に対応するためにある機能です。DELPHIではIntefaceオブジェクトが無効(?)になるとき自動的に'_Release'が呼ばれ、コピーされるときに'_AddRef'が呼ばれます。この挙動は非常に便利で、いわゆる参照カウンターを持つオブジェクトとして扱うことができます。私は解放のタイミングが把握しにくいオブジェクトを扱う時などに使います。Interfaceを持つオーバーヘッドを考慮しても、メリットの方が大きいと考えます。

    まずは以下のようにIntefaceを書いてみます。
      IType = interface
        procedure DoTest() ;
      end;
    
      TTestType = class(IType)
      public
        procedure DoTest() ;
      end;
    
    DELPHIではクラスを継承するようにInterfaceを追加できます。Interfaceはあくまでインターフェイス(部品と部品を繋ぐ接点)なので、扱うにはどこかに実体が無いといけません。通常、実体はクラスの方で実装します。
    実際に上記の例をビルドすると、QueryInterface,_AddRef,_Releaseが無いとエラーが出ます。DELPHIでは、どのInterfaceIInterfaceを継承しています。そのIInterfaceではQueryInterface,_AddRef,_Releaseが宣言されているため、クラスにこの3つのメソッドが実装されていないとコンパイルエラーがでます。
    DELPHIには便利なTInterfacedObjectというクラスが用意されています。これは、QueryInterface,_AddRef,_Releaseがすでに実装されているクラスです。
    次のようにTTestTypeを書き換えます。
      TTestType = class(TInterfacedObject,IType)
      public
        procedure DoTest() ;
      end;
    
    使用する際は以下のようにします。
    var
      intf : IType ;
      entity : TTestType ;
    begin
      entity := TTestType.Create() ;
      intf := entity ;
      intf.DoTest() ;  // <- run
    end ;
    
    見て分るように単純にITypeの変数にTTestTypeのオブジェクトをコピーするだけで使えるようになります。
    ここでオブジェクトの破棄について考えます。
    1)上記のような参照カウンターを持つInterfaceを使用する際は、実体側のオブジェクト(上の例だと、entity)でオブジェクトの解放(free())やコピー・複製はしてはいけません。Intefaceの参照カウンターでオブジェクトのライフタイム制御しているので、別の次元でライフタイム操作を行うのは賢明とは思えません。
    2)1)を踏まえて上記の例を考えます。Inteface変数は破棄される際に_Releaseが呼ばれ、参照カウンターが減りますので、intf変数は関数を抜ける際に参照カウンターはゼロとなり、実体であるentityオブジェクトは破棄されます。entity.free()など呼ぶと、実行時エラーが起きます。

    class functionを使って以下のように書くと、うまく実体が隠せます。(ただし、これだけでは完全には隠せません・・・何か良い方法があるのでしょうか?コンストラクタを隠せばいいように感じますが・・・)
      TTestType = class(TInterfacedObject,IType)
      public
        procedure DoTest() ;
      public class
        function GetIntrf() : IType ;
      end;
    
    ...
     
    class function TTestType.GetIntrf: IType;
    begin
      exit(TTestType.Create()) ;
    end;
    
    次はもう少しだけDELPHIInterfaceを使ってみます。

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

    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クラスが用意されています。
    今後、試してみたいと思っています。

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


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

    2011年11月11日金曜日

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

    今回は非同期送信処理を作ってみます。基本的な構造は受信側と同じでModuleThreadクラスを使ってマルチスレッド化しています。送信処理の流れは名前解決→接続(コネクション)→データの送信→返信データの受信となります。

    以下送信側のソースコードです。

    #pragma once
    
    #ifndef __ASIO_SENDER_MODULE__
    #define __ASIO_SENDER_MODULE__
    
    #include <iostream>
    #include <boost/asio.hpp>
    #include <boost/bind.hpp>
    #include <boost/function.hpp>
    #include <boost/thread.hpp>
    #include <boost/shared_ptr.hpp>
    #include <boost/lexical_cast.hpp>
    
    namespace thorny_road{
    
    using namespace boost::asio ;
    
    // Simple Message data socket
    class SimpleMsg
    {
    private :
        std::string msg ;
        boost::asio::streambuf buf ;
    public:
        SimpleMsg(const std::string msg) : msg(msg)
        {
            std::ostream os(&buf) ;
            os.write((char *)(msg.c_str()),msg.size()) ;
    
            char ch = '\n' ;
            os.write(&ch,sizeof(char)) ; // Append Return Code
        }
    
        ~SimpleMsg() {}
    
        boost::asio::streambuf& rdbuf()
        {
            return buf ;
        }
    } ;
    
    
    template<typename SOK_PTYPE>
    class SenderSession : private boost::noncopyable
    {
    private:
        io_service& io ;
        ip::tcp::socket socket ;
        boost::asio::streambuf buf ;
        std::string address ;
        short port ;
        boost::shared_ptr<ip::tcp::resolver> resolver ;
    public:
        SenderSession(std::string& address,short port, SOK_PTYPE pdata,io_service& io)
            : io(io),socket(io),address(address),port(port)
        {
            // make resolver
            resolver.reset(new ip::tcp::resolver(io) );
            // make query
            ip::tcp::resolver::query query(address,boost::lexical_cast<std::string>(port));
    
            resolver->async_resolve(
                query,
                boost::bind(&SenderSession::resolve_ok, this,
                    boost::asio::placeholders::error,
                    pdata,
                    boost::asio::placeholders::iterator));
        }
    
        ~SenderSession(){}
    
    private:
        void resolve_ok(const boost::system::error_code e,SOK_PTYPE pdata,
                boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
        {
            if (!e)
            {
                // Enqueue Connect Handler
                boost::asio::async_connect(
                    socket,
                    endpoint_iterator,
                    boost::bind(&SenderSession::connect_ok, this,
                        boost::asio::placeholders::error,
                        pdata,
                        endpoint_iterator));
    
            }
            else if (e == boost::asio::error::operation_aborted)
            {
                // abort
                std::cout << "connection abort" ;
                std::cout << e.message() ;
                delete this;
                return ;
            }
            else
            {
                std::cout << "error" ;
                std::cout << e.message() ;
                delete this;
                return ;
            }
        }
    
        void connect_ok(const boost::system::error_code e,SOK_PTYPE pdata,
                boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
        {
            if (!e)
            {
                // Enqueue Write Handler
                boost::asio::async_write(
                    socket,
                    pdata->rdbuf(),
                    boost::bind(&SenderSession::receive_read, this,
                        boost::asio::placeholders::error,
                        pdata));
               
            }
            else if (e == boost::asio::error::operation_aborted)
            {
                // abort
                std::cout << "connection abort" ;
                std::cout << e.message() ;
                delete this;
                return ;
            }
            else if (endpoint_iterator != boost::asio::ip::tcp::resolver::iterator())
            {
                // failed to resolve endpoint.
                boost::asio::async_connect(
                    socket,
                    endpoint_iterator,
                    boost::bind(&SenderSession::connect_ok, this,
                        boost::asio::placeholders::error,
                        pdata,
                        ++endpoint_iterator)); // Try next resolved endpoint name.
            }
            else
            {
                std::cout << "error" ;
                std::cout << e.message() ;
                delete this;
                return ;
            }
        }
    
        void receive_read(const boost::system::error_code e,SOK_PTYPE pdata)
        {
            if (!e)
            {
                // Call Self until end of receive data.
                async_read(
                    socket,
                    pdata->rdbuf(),
                    boost::asio::transfer_at_least(1),
                    boost::bind(&SenderSession::receive_read,this,
                        boost::asio::placeholders::error,
                        pdata)) ;
            }
            else if (e == boost::asio::error::operation_aborted)
            {
                // abort
                std::cout << "connection abort" ;
                std::cout << e.message() ;
                delete this;
                return ;
            }
            else if (e != boost::asio::error::eof)
            {
                std::cout << "error" ;
                std::cout << e.message() ;
                delete this;
                return ;
            }
            else
            {
                delete this ; // end of session.
            }
        }
    
    
    public:
        ip::tcp::socket& getSocket()
        {
            return (socket) ;
        }
    
    } ;
    
    class SenderModule : private boost::noncopyable
    {
    private:
        io_service& io ;   
        short port ;
        boost::mutex _lock ; // Lock Object
    public:
        SenderModule(io_service& io, const short port)
            :io(io),port(port)
        {}
    
        ~SenderModule() {}
    
        template<typename U>
        void send(std::string address, U pdata)
        {
            boost::mutex::scoped_lock cs(_lock) ; // Lock until escaping this scope
    
            SenderSession *session = new SenderSession(address,port,pdata,io) ;
        }
    
    } ;
    
    } // namespace thorny_road
    #endif //__ASIO_SENDER_MODULE__
    
    メイン
    #include "stdafx.h"
    #include "SenderModule.hpp"
    #include "ModuleThread.hpp" // for threading
    #include <boost/date_time/posix_time/posix_time.hpp>
    #include <boost/bind.hpp>
    
    
    using namespace thorny_road ;
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        ModuleThread<SenderModule> sender(2085,2) ; // create module.
    
        boost::shared_ptr<SimpleMsg> msg(new SimpleMsg("[message]")) ;
        sender.getModule()->send("localhost",msg) ;
    
        boost::this_thread::sleep(boost::posix_time::milliseconds(1000)) ;
    
        return 0;
    }
    
    今回は単純に文字列を送っています。上の例では"[message]"を送信しています。送信先はsend(...)の第一引数にアドレスを入れます。今回は"localhost"に送るようにしています。第二引数にはパケットデータクラスのポインタを入れます。パケットデータクラスは以下のようにバッファを参照するためのメソッド( rdbuf() )が用意されていればコンパイルが通ります(今のところは・・・)。
    class SimpleMsg
    {
    private :
        std::string msg ;
        boost::asio::streambuf buf ;
    public:
        SimpleMsg(const std::string msg) : msg(msg)
        {
            std::ostream os(&buf) ;
            os.write((char *)(msg.c_str()),msg.size()) ;
    
            char ch = '\n' ;
            os.write(&ch,sizeof(char)) ; // Append Return Code
        }
    
        ~SimpleMsg() {}
    
        boost::asio::streambuf& rdbuf()
        {
            return buf ;
        }
    } ;
    
    今回の例では単純に文字列をバッファストリームに書き込んでいます。受信側では最初の受信では'\n'まで読み込むので最後に'\n'を足しています。
    また、ModuleThreadgetModule()は前回まではモジュールクラスのポインタを返していましたが、スマートポインタで返すように変更しています。私はポインターは面白くて好きですが、苦い経験も多々あるので最近はスマートポインタを多用しています。DELPHIでは参照カウンターを持っているInterfaceをよく使います。

    受信側のプログラムが実行している状態で、送信側のプログラムを実行すると送受信が行われますが、送信セッションが受信待ち状態のままになります。これは受信側のセッションが終了していないためです。受信側で返信した後セッション閉じるようにすれば送信側もセッションが切れます。
        void write_ok(const boost::system::error_code e)
        {
            if (!e)
            {
                // start() ; // restart
                delete this; // finish this packet
            }
    
    DELPHIにもC++のTemplateのような機能が欲しいと思うことが多々ありますが、ビルドが高速というのがDELPHIの良い点のひとつなので難しいところです。

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

    2011年11月10日木曜日

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

    前回までのソースコードでboost::noncopyableの使い方が間違えていました。boost::noncopyableは簡単にクラスのコピーコンストラクタとコピー代入演算子を隠蔽するものですが、privateで継承しないと意味が無かったです。

    class ServerModule : private boost::noncopyable

    今回は、接続後のやり取りを分けてみたいと思います。行うことはシンプルで、非同期処理関数をクラスにまとめて分けます。

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

    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 ServerSession : private boost::noncopyable
    {
    private:
        io_service& io ;
        ip::tcp::socket socket ;
        boost::asio::streambuf buf ;
    
    public:
        ServerSession(io_service& io) : io(io),socket(io) {}
    
        ~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
                if (tmp == "end") { 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) ) ;
            }
        }
    
        void write_ok(const boost::system::error_code e)
        {
            if (!e)
            {
                start() ; // Restart
            }
        }
    
    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) ;
            // Enqueue Accept Handler
            accept.async_accept(
                session->getSocket(),
                boost::bind(&ServerModule::accept_ok,this,_1) ) ;
        }
    
        void accept_ok(const boost::system::error_code e)
        {
            if (!e)
            {
                session->start() ;
                start_accept() ;
            }
            else
            {
                // error
                delete session ;
                session = NULL ;
                return ;
            }
        }
    
    } ;
    
    } // namespace thorny_road
    #endif //__ASIO_SERVER_MODULE__


    メインは変更はありません。接続後の処理はServerSessionクラスで行うようになりました。このため、SocketはServerSessionが管理するようになります。
    ServerModuleでは接続完了後(accept_ok(...)が呼ばれると)、start_accept()を呼び、新たにSessionを作成し接続待ち状態になります。
    ServerSessionでは前回同様入力をそのままエコーバックします。ただし、入力が"end"の場合、ServerSessionオブジェクトを破棄され、接続を切断します。

    if (tmp == "end") { delete this; return; }

    また、今回は簡単なboost::system::error_codeの処理をいれています。ハンドラーの引数であるboost::system::error_codeは正常の場合、0になります。

    ようやく「らしく」なってきました。次はどうしようか考え中です。

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

    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()が無限にループするようになります。

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

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

    2011年11月8日火曜日

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

    C++とBoostの勉強のため、Boost::Asioを使ってみたいと思います。おかしな部分も多いので、ご指摘頂けると嬉しいです。
    Boostのインストール等は省略します。環境は以下です。

    -WindowsXP
    -Boost1.47.0

    BoostのAsioはネットワークやローレベルのIOを扱うための同期・非同期モデルです。これを使えば、同期・非同期でのネットワーク処理などが行えます。
    (# http://www.boost.org/doc/libs/1_47_0/doc/html/boost_asio.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 ;
        ip::tcp::iostream buf ;
    public:
        
        ServerModule(io_service& io, const short port)
            :io(io),accept(io,ip::tcp::endpoint(ip::tcp::v4(),port))
        {
            start_accept() ;
        }
    
        void start_accept()
        {
            accept.accept( *buf.rdbuf() ) ;
            std::string tmp ;
            buf >> tmp ; // get input stream
            buf << tmp <<std::endl ; // retrun as it is
        }
    
    
    } ;
    
    } // 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
    
        return 0;
    }

    勉強を兼ねてServerはクラスで構築しました。また、ヘッダーファイルにコードを書いています。その方が何かと載せやすいというのと、DELPHIもよく使うため慣れているからです。

    まず最初にio_serviceを作成します。それを引数にServerModuleのインスタンスを作成しています。ServerModuleのコンストラクタが呼ばれると、start_accept()関数が呼ばれて接続待ちになります。accept(...)は同期処理になるので、接続が来るまで待つことになります。

    これを実行すると、インプット待ち状態になります。
    以下のようにTelnetでアクセスすると、接続できるようになります。
    telnet localhost 2085

    簡単にネットワークのプログラムが書けました。Boostは便利ですね。

    次は非同期処理に挑戦したいですね。

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