boost.Asioを半年使っわかったこと

[C++ Advent Calendar 2015](http://www.adventar.org/calendars/926)も21日目
もうすぐ　メリー・クリスマスですね。
昨日は
[Iさん](https://twitter.com/wx257osn2)の [C++ AMPでGPGPU！](http://qiita.com/wx257osn2/items/6ad749f194db12c98467)でした

わたしは残念ながらDirectX11が使えるPCを持っていないので AMPは試せないのですが
GPGPUは楽しいですね

わたしは、boost.asioのまとめです。



#はじめに
##なぜ boost.Asioが必要だったか
お仕事の関係で、お客さんにC++で高速なステートフルサーバを作って欲しい　C++で！
という要望が来たので。

##boostに頼る理由
C++11、14 と、標準ライブラリが増え、threadも使えるようになった
が、未だネットワーク通信関連は標準ライブラリになっていないため
boost.Asioを採用した
boost.Asioはネットワークに特化したライブラリではなく、非同期I/Oのライブラリで
タイマー、ファイルの非同期等、色々と利用できる

非同期I/Oなので、大抵の場合はマルチスレッド型よりも速く、シングルスレッドなのでロックが少なくなる


#Asioのポイント

###非同期関数と同期間数
Asioでは、同期関数と非同期関数が用意されている

非同期のasync_readと、同期のread
当然、非同期関数をメインに使っていくことになる

###メンバ関数とフリー関数
read、write、async_read、async_write系には、メンバ関数とフリー関数がある
どちらでも問題なく動作するが
恐らく今のコーディングスタイルでは、フリー関数をメインで使うのが良いと思われる
使った限りでは、あまり動作に違いは感じられなかった
（詳しい方、使い分け等ありましたら　教えて下さい）

###3つの async_read/async_write
read/write関数は3種類ずつ存在する
####ノーマル
read()、async_read()、write()、async_write()　が該当する
標準的なread/writeをする。
#### xxx_at
read_at()、async_read_at()、write_at()、async_write_at() が該当する
read/writeを行うバイト数を指定する
大量データを小分けにして処理したい時に使うと思われる
#### xxx_until
read_until()、async_read_until()、write_until()、async_write_until()が該当する
指定した文字列が出現するまで read/writeを行う。正規表現可能。
例えば 改行コードで毎にread/writeする場合に便利

###ハンドラと寿命
async関数を呼ぶと関数自体は ハンドラを登録し 直ちに終了する
指定された動作が完了したらハンドラが呼び出される（非同期動作）
それ故に関数内で生成したオブジェクトは、ハンドラが実行される時には既に存在していない事になる
その為に　shared_ptrを使うと、サンプルにあった
http://qiita.com/YukiMiyatake/items/f4641c54151a18c362f9
で少しカイセツした
要は、ハンドラをラムダで定義し、自分のshared_ptrをキャプチャすると、関数終了時まで残る

###コードの可読性
以前にnode.js を使ったことがあったので、読めたが
最初に見たら少し戸惑うかもしれない

```cpp:async_read
boost::asio::async_read(socket_, boost::asio::buffer(data, data.length()),
    [this](boost::system::error_code ec, std::size_t ){
       if (!ec) {
              boost::asio::async_write(socket_,
                 boost::asio::buffer(data, data.length()),
                     [this](boost::system::error_code ec, std::size_t ){
                        if (!ec){
                           .....
                        }else{
                    //writeのエラー処理
                }
                 });
              }
       }else{
           // readのエラー処理・・
       }
   });
```

非同期処理をすると、上記のような えらいコードが出来上がってしまう

それを避けるために色々な手法が存在する
恐らくは、ある程度の規模の開発だと futureかcoroutineを使う事がベストだと思われる
futureはnode.jsでもあったし同じようなものだと思う
今回は 次期標準ライブラリ候補というcoroutineを使用した


###coroutine
最近いろいろな言語で実装されている、軽量スレッド。
どうやら次期C++ではコルーチンも標準化されそう。
なので、既に標準化されたfutreではなくcoroutineを試した

コルーチンはstackles_coroutineとstackful_coroutineの2種類あり、簡単なstacklessから試した

####stackles_coroutine
中身を見たら、switch caseのマクロであった
ラベルを使い、ステートマシンで処理を切り替える原始的な方法
ものすごく軽いでしょう　Jumpしてるだけなので
ただし、名前のとおり、他のコルーチンとスタックを共有しているので
ローカル変数が使いにくいし、例外のキャプチャ等も面倒そうなので本
大きなシステムには向かないと思ったので　不採用
小さいコードには非常に便利

```cpp:stackles
reenter(this){
    do{
        yield acceptor_->async_accept(socket_,*this);
        fork server(*this)();
    }while(is_parent());

    yield socket_.async_read_some(asio::buffer(buffer_), *this);
    yield socket.async_write_some(asio::buffer(buffer_), *this);
    // ...　ここに処理をかいていく
};
```

####stackful_coroutine
これは強力であった。非同期の 悪魔のような });　が無くなった
コルーチン毎にstackを生成するので、マルチスレッドと同じ感覚で書ける
しかも、シングルスレッドで動作することが保証されるため
面倒な排他のことを考える必要もない！
asioを使う際には 一度stackful_coroutineを試してみる事を強くおすすめする
コードは

```cpp:stackful
boost::asio::spawn(strand_, [this](boost::asio::yield_context yield){
    async_read(socket_, asio::buffer(buffer_), yield);
    async_write(socket_, asio::buffer(buffer_), yield);    
    // ... ここにコードを追加する
});
```

stacklesでも同じだが、async_xxx と書いても、見かけ上は 同期しているように見える
とても人間にわかりやすい

asio::spawnを呼ぶと、コルーチンを作成する。
第一引数は おなじみio_serviceで、第二引数にコルーチンが生成された時の関数を登録する
実際には　async_acceptをした際に spawnをする流れになるだろう

簡単に動作を説明すると、async_xxx を呼んだ時に、自主的に軽量タスクを解放する（ノンプリエンプティブ）
他にシグナルになっている軽量タスクがあれば、そこにタスクスイッチする。
stackfulの場合は、軽量タスクがそれぞれにスタックフレームを持っているため、ローカル変数等も使うことが可能

###例外処理
通常、stackful_coroutineでは例外が飛んで来るが、yieldを yield[boost::system::error_code] にすると、例外を発生させずに
エラーコードが返ってくる
必要に応じて使い分けられるので便利


###タイムアウト
これは少し苦労した。
流れとしては
asio::deadline_timer を作成し
asio::deadline_timer.expires_from_now にタイムアウト時間を設定すし
asio::deadline_timer.async_wait を呼ぶと、タイムアウトorキャンセル時にハンドラが呼ばれるが
この後、例えばread処理を行い、readが終わればtimerをキャンセルする。
もしreadされる前に タイムアウトした場合は タイムアウトハンドラが呼ばれるので、そこでsocketをキャンセル
非常に　面倒な処理である
これが正しいのかわからないが　現状正しく動作しているので、これで行っている

```cpp:timeout
asio::deadline_timer timer; 
timer.expires_from_now(boost::posix_time::seconds(10));
timer.async_wait(
    [&socket_](const boost::system::error_code &ec) {
        if (ec == boost::asio::error::operation_aborted) {
            // キャンセルされた場合
            cout << “cancel” << endl ;
        } else {
            // タイムアウト時はソケットをキャンセルし readをやめる
            socket_.cancel();
            cout << “timeout” << endl ;
        }            
    });
async_read(socket_, boost::asio::buffer(buffer_), yield);
// readが先におわったので、タイマーをキャンセル
timer.cancel();
```


最後の async_readをタイムアウト付きで呼びたい。今回は10秒待って データが来なければタイムアウトする

まず deadline_timerを作成し10秒に設定する
その後 タイマーのasync_waitで、タイムアウト時のハンドラを登録する。
その後 async_readを行なう。

1. 時間内にasync_readが成立した場合（ノンタイムアウト）
async_readが終わり、timer.cancel() が実行される
タイマーのハンドラが　operation_aborted で呼ばれる

2. 時間内にasync_readが成立しない（タイムアウト）
タイマーハンドラが timed_out で呼ばれる
socket.cancel() で、async_readを終了させる
（timer.cancelが呼ばれるが それが気持ち悪いなら フラグで呼ばないよう制御すればいい）

async_readにタイムアウト設定出来るオーバーロードが欲しいと思った
（無いので 関数作った）


# イベントハンドラ
Asioはネットワーク通信として主に使われるが
非同期I/O のライブラリなので、ネットワーク以外にも使える

例えば 1秒毎に Update関数を呼ぶ無限ループは

```cpp:イベントハンドラ
boost::asio::spawn(io_service_, [&self](boost::asio::yield_context yield) {
  for (; ;) {
    timer.expires_from_now(boost::posix_time::seconds(1));
    timer.async_wait(yield[ec]);

    Update();
  }
});
```

こんな形で簡単に実装できる

もっとちゃんとしたサンプルは、高橋晶さんの
http://faithandbrave.hateblo.jp/entry/20110530/1306739714

このあたりを。

ネットワーク通信ではイベント処理的に処理する事も多いので
そういう場合にも役に立ちます


#まとめ
とりあえず、この程度の知識で、実務で耐えうる非同期サーバを構築する事が可能
シングルスレッドなので、慣れれば マルチスレッド方式より楽に作れると思う
大規模なシステムであればcoroutineかfutureを使うことをおすすめ
同期関数を使って従来のマルチスレッド方式も出来るんだよ

思ったより素直なライブラリなので、わりと簡単に扱えると思います



# 番外編：Asioするうえで便利だったもの

## ラムダ
非同期ライブラリを使うにはラムダがとにかく便利。いちいち関数オブジェクト作りたくない

## functional、bind
ラムダを使えば必ずセットに出てくるもの。

## shared_ptr
非同期ゆえに、オブジェクトの生存期間が難しいので、shared_ptrは重宝します

## tuple、tribool
webプログラミングをしていると、ステータスコードとメッセージが欲しいとか
データ取得時に true、false以外にも まだバッファ残っているという状態が欲しかったり
普段のコード以上に　tupleやtriboolが便利だと思った

##　副作用
永遠の課題かもしれない。
Asioに限らず、副作用を減らす事は保守性にも関わるし
遅延評価など高速化にも関わるはずで
特に非同期コードを書いていると、副作用が多いと苦労した



#さいごに

つぎは22日目 [hgodaiさん](https://twitter.com/hgodai)

お楽しみに！