asio 02
はじめに
前回作った名前解決をするコードを、asio の真価を発揮させて非同期にしてみよう。
うんちく
もしあなたが名前解決をするコードを asio を使わずに getaddrinfo や gethostbyname を使って書いたことがあるのなら、以下のようなことをご存じかもしれない。
- getaddrinfo、gethostbyname は connect、send、recv などの関数と違って非同期にすることができないということ
- gethostbyname が返すバッファの扱いには十分注意が必要
ちなみに、gethostbyname に関しては既によく論じられている。
- Geekなぺーじ : gethostbynameの落とし穴
- Manpage of GETHOSTBYNAME
- gethostbyname macro (wsipv6ok.h) | Microsoft Docs
対策状況はそれぞれ以下のようになっているようだ。
- getaddrinfo、gethostbyname は connect、send、recv などの関数と違って非同期にすることができないということ
- 内部でワーカースレッドを作りそこから呼び出す
- よくある手なのでやっぱりそうだよね、という感想
- 内部でワーカースレッドを作りそこから呼び出す
- gethostbyname が返すバッファの扱いには十分注意が必要
サンプルコード
#define _WIN32_WINDOWS 0x0400 #define _WIN32_WINNT 0x400 #include <iostream> #include <boost/array.hpp> #include <asio.hpp> using asio::ip::tcp; void on_resolve( const asio::error_code& error, tcp::resolver::iterator endpoint_iterator ) { if ( ! error ) { tcp::resolver::iterator end; while ( endpoint_iterator != end ) { tcp::endpoint endpoint = endpoint_iterator->endpoint(); std::cout << " capacity: " <<endpoint_iterator->endpoint().capacity() << std::endl; std::cout << " data : " <<endpoint_iterator->endpoint().data() << std::endl; std::cout << " port : " <<endpoint_iterator->endpoint().port() << std::endl; std::cout << " size : " <<endpoint_iterator->endpoint().size() << std::endl; // print address info. asio::ip::address address = endpoint.address(); std::cout << " address : " << address.to_string() << std::endl; std::cout << " is_v4 : " << address.is_v4() << std::endl; std::cout << " is_v6 : " << address.is_v6() << std::endl; std::cout << std::endl; endpoint_iterator++; } } else { std::cerr << error.message() << std::endl; } } int main( int argc, char* argv[] ) { try { if ( argc != 3 ) { std::cerr << "Usage: resolver <host> <service>" << std::endl; return 1; } asio::io_service io_service; tcp::resolver resolver( io_service ); tcp::resolver::query query( argv[1], argv[2] ); // print query info. std::cout << query.host_name() << std::endl; std::cout << query.service_name() << std::endl; std::cout << std::endl; // start to resolve. resolver.async_resolve( query, on_resolve ); io_service.run(); } catch ( std::exception& e ) { std::cerr << e.what() << std::endl; } return 0; }
まとめ
asio の名前解決は、名前解決関数にまつわる問題を回避して安全に使うことができる。
それと、ソケット関数と asio の対応表があったので必要に応じて参考にすること。
懸念点
今回は特になし。
アプリケーション設計のガイドライン
名前解決によって複数のエンドポイントを取得できたときの振る舞いをどうするか
- 最初の一つだけを処理対象とする(非推奨)
- 全てを処理対象とする(推奨)
非同期で名前解決を行った場合にどれだけ待機するか
- 名前解決処理が何らかの処理結果を返すまで待機する(推奨)
- アプリケーションが定めた時間だけ待つ(非推奨)
- ネットワークの状態が思わしくないときに反応が遅いからといって単純に待機時間を短縮すればいいというわけではない
- ソフトウェアファイアウォールによって警告ダイアログが出ているだけの場合もある
- 名前解決に関する詳しく知識がない場合は極力これを避けること
- その場合処理結果が帰ってくるまでの時間は OS の実装に依存する
- 名前解決処理が何らかの処理結果を返すのを待機しつつユーザからキャンセル指示があった場合は即終了する
- かといってすぐにプログラムを終了してはいけない
- 名前解決処理は途中で中断できない
- ユーザからキャンセル指示があった場合は「終了中」状態に移行しすべての処理が正常に終わるまで待機してからプログラムを終了する
複数の IP アドレスからどれを選択するか
次回に向けて
さぁ次はソケットを作っていよいよ通信といきたいところだが、それはサンプルが豊富なので割愛し、非同期を扱う上で欠かすことのできない io_service について取り上げる。