「Boost.Asio C ++ネットワークプログラミング。」 第6章:-その他の機能

みなさんこんにちは!

John Torjoの本Boost.Asio C ++ Network Programmingの翻訳を続けています。



内容:





この章では、Boost.Asioのあまり知られていない機能のいくつかを見ていきます。 stdストリームとstreambufオブジェクトは、使用するのが少し難しい場合がありますが、自分でわかるように、それらには利点があります。 最後に、Boost.Asioへのかなり遅い追加が表示されます。コルーチンを使用すると、非同期コードを作成できますが、読みやすい(同期のように) これは非常に素晴らしい機能です。







stdストリームおよびstd入力/出力バッファー





このセクションで記述されていることを理解するには、STLストリームやSTL streambufなどのオブジェクトに精通している必要があります。

Boost.Asioには、I / Oを操作するための2種類のバッファーがあります。



この本を通して、基本的に次のようなものを見ました。



size_t read_complete(boost::system::error_code, size_t bytes){ ... } char buff[1024]; read(sock, buffer(buff), read_complete); write(sock, buffer("echo\n"));
      
      





通常、これで十分です。 ただし、より柔軟性が必要な場合は、 streambuf



を使用できます。 以下は、 streambuf



オブジェクトでできる最も単純で最悪なことです。



 streambuf buf; read(sock, buf);
      
      





この読み取りは、 streambuf



オブジェクトがstreambuf



になるまで続きますstreambuf



オブジェクトは、より多くのスペースを収容するためにそれ自体を再配布できるため、基本的に、接続が閉じられるまで読み取りが行われます。 read_until



関数を使用して、最後の文字まで読み取ることができます。



 streambuf buf; read_until(sock, buf, "\n");
      
      





ここで、読み取りは文字「\ n」まで進み、読み取られたものがバッファに追加され、読み取り機能が終了します。 streambuf



オブジェクトに何かを書き込むには、次のようなことを行います。



 streambuf buf; std::ostream out(&buf); out << "echo" << std::endl; write(sock, buf);
      
      





とても簡単です。STLストリームを作成し、構築中にstreambufオブジェクトをそこに配置し、送信したいメッセージをwrite



から、 write



関数を使用してバッファーの内容を送信する必要があります。



Boost.AsioおよびSTLストリーム





C Boost.Asioは、STLストリームとネットワークを統合して素晴らしい仕事をしました。 つまり、STLをすでに広く使用している場合は、オーバーロードされた演算子>>および<<を含む多くのクラスが既にあるはずです。 ソケットへの読み書きは、公園を散歩するよりも魅力的です。

次のコードスニペットがあるとします。



 struct person { std::string first_name, last_name; int age; }; std::ostream& operator<<(std::ostream & out, const person & p) { return out << p.first_name << " " << p.last_name << " " << p.age; } std::istream& operator>>(std::istream & in, person & p) { return in >> p.first_name >> p.last_name >> p.age; }
      
      





ネットワークを介した人間のデータの転送は、以下に示すように簡単です。



 streambuf buf; std::ostream out(&buf); person p; // ... initialize p out << p << std::endl; write(sock, buf);
      
      





反対側でも同じように簡単に読むことができます。



 read_until(sock, buf, "\n"); std::istream in(&buf); person p; in >> p;
      
      





streambuf



オブジェクトともちろん対応するstd::ostream



を書き込みまたはstd::istream



を読み取りに使用することの本当に良い面は、通常と見なされるコードを書くことになります。



最後に、次のコードを使用して、コンソールにstreambuf



オブジェクトの内容をダンプする非常にクールなトリックが知られています。



 streambuf buf; ... std::cout << &buf << std::endl; // dumps all content to the console
      
      





同様に、コンテンツを文字列に変換するには、次のコードスニペットを使用します。



 std::string to_string(streambuf &buf) { std::ostringstream out; out << &buf; return out.str(); }
      
      







クラスstreambuf



前述したように、 streambuf



std::streambuf.



から派生していstd::streambuf.



std :: streambufのように、コピーコンストラクターはありません。

さらに、次のようないくつかの追加機能があります。



最後の2つの機能を除いて、残りはそれほど簡単に理解できません。 まず、ほとんどの場合、 streambuf



インスタンスを引数として送信し、以下に示すように、独立した関数を読み書きします。



 read_until(sock, buf, "\n"); // reads into buf write(sock, buf); // writes from buf
      
      





前のフラグメントに示したように、バッファ全体を独立した関数に送信する場合、関数はまずバッファのサイズを増やすかどうかを確認し、入力ポインタと出力ポインタを探す必要があります。 つまり、読み取るデータがあれば、それを読み取ることができます。

例:



 read_until(sock, buf, '\n'); std::cout << &buf << std::endl;
      
      





前のスニペットは、ソケットから読み取った内容をリセットします。 次の例では、何かをダンプしません。



 read(sock, buf.prepare(16), transfer_exactly(16) ); std::cout << &buf << std::endl;
      
      





バイトは読み取られますが、ポインターは移動しません。 以下に示すように、自分で移動する必要があります。



 read(sock, buf.prepare(16), transfer_exactly(16) ); buf.commit(16); std::cout << &buf << std::endl;
      
      





同様に、 streambuf



オブジェクトに書き込みたい場合、および独立した書き込み関数を使用する場合は、次のコードフラグメントを使用します。



 streambuf buf; std::ostream out(&buf); out << "hi there" << std::endl; write(sock, buf);
      
      





次のコードは、 hi there



3回hi there



を送信します。



 streambuf buf; std::ostream out(&buf); out << "hi there" << std::endl; for ( int i = 0; i < 3; ++i) write(sock, buf.data());
      
      





これは、バッファが破壊されることはなく、データはそこに残るためです。 データを破棄する場合は、これがどのように実装されているかを確認してください。



 streambuf buf; std::ostream out(&buf); out << "hi there" << std::endl; write(sock, buf.data()); buf.consume(9);
      
      





結論として、 streambuf



インスタンス全体を処理することを選択する必要があります。 微調整する場合は、前の機能を使用します。

読み取りと書き込みに同じstreambufインスタンスを使用できる場合でも、読み取り用と書き込み用の2つの別々のインスタンスをお勧めします。 それはより簡単に、より明確に知覚され、多くの起こりうる間違いを避けます。



streambuf



オブジェクトを操作する独立した関数



次のリストは、 streambuf



オブジェクトを操作するBoost.Asioの独立した関数を示しています。



母音まで読みたいとしましょう:



 streambuf buf; bool is_vowel(char c) { return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u'; } size_t read_complete(boost::system::error_code, size_t bytes) { const char * begin = buffer_cast<const char*>( buf.data()); if ( bytes == 0) return 1; while ( bytes > 0) { if ( is_vowel(*begin++)) return 0; else --bytes; } return 1; } ... read(sock, buf, read_complete);
      
      





たとえば、正規表現を使用する場合、これは非常に簡単です。



 read_until(sock, buf, boost::regex("^[aeiou]+") );
      
      





または、例を少し変更して、 match_condition



関数を機能させることができます。



 streambuf buf; bool is_vowel(char c) { return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u'; } typedef buffers_iterator<streambuf::const_buffers_type> iterator; std::pair<iterator,bool> match_vowel(iterator b, iterator e) { while ( b != e) { if ( is_vowel(*b++)) return std::make_pair(b, true); } return std::make_pair(e, false); } ... size_t bytes = read_until(sock, buf, match_vowel);
      
      







コルーチン



2009年から2010年頃のBoost.Asioの作成者は、非同期アプリケーションの作成をさらに容易にするコルーチンの非常にクールなアイデアを実装しました。

これにより、非同期アプリケーションを簡単に記述でき、アプリケーションが順番に記述されているかのように、制御フローを簡単にたどることができます。







最初のケースでは、通常のアプローチが表示されます。 コルーチンを使用すると、2番目のケースにできるだけ近くなります。

簡単に言えば、コルーチンを使用すると、複数のエントリポイントを使用して、関数内の特定の場所で実行を一時停止および再開できます。

コルーチンを使用する場合は、2つのヘッダーファイルを含める必要があります。これらのファイルは、 boost/libs/asio/example/http/server4: yield.hpp



およびboost/libs/asio/example/http/server4: yield.hpp



のみ見つけることができます。 Boost.Asioでは、2つのマクロと1つのクラスが定義されています。



よりよく理解するために、いくつかの例を検討してください。 第4章からアプリケーションを再実装します。これは、システムに入り、応答し、他のどのクライアントがログに記録されているかを通知する単純なクライアントです。

メインコードは次のようになります。



 class talk_to_svr : public boost::enable_shared_from_this<talk_to_svr> , public coroutine, boost::noncopyable { ... void step(const error_code & err = error_code(), size_t bytes = 0) { reenter(this) { for (;;) { yield async_write(sock_, write_buffer_, MEM_FN2(step,_1,_2) ); yield async_read_until( sock_, read_buffer_,"\n", MEM_ FN2(step,_1,_2)); yield service.post( MEM_FN(on_answer_from_server)); } } } };
      
      





最初に変更されたのは、 connect(), on_connect(), on_read(),do_read(), on_write(), do_write()



などconnect(), on_connect(), on_read(),do_read(), on_write(), do_write()



多数のメンバー関数が消失したstep()





関数の本体はreenter(this) { for (;;) { }}



ます。 reenter(this)



を最後に実行したコードと考えることができるため、次のコードを呼び出すことができます。

reenter



ブロック内では、いくつかの進行中の呼び出しを確認できます。 関数が最初に起動されると、 async_write



関数がasync_write



、2回目はasync_read_until



関数がasync_read_until



、3回目はservice.post



関数、4回目はasync_write



れます。

for(;;) {}.



インスタンスを決して忘れないでくださいfor(;;) {}.



次のコードを見てみましょう。



 void step(const error_code & err = error_code(), size_t bytes = 0) { reenter(this) { yield async_write(sock_, write_buffer_, MEM_FN2(step,_1,_2) ); yield async_read_until( sock_, read_buffer_, "\n",MEM_FN2(step,_1,_2)); yield service.post( MEM_FN(on_answer_from_server)); } }
      
      





前のコードフラグメントを3回使用した場合、関数を入力してservice.post



を実行します。 4回目は、 service.post



を渡し、何もしません。 同じことが、5回目と次のすべての場合に発生します。



 class talk_to_svr : public boost::enable_shared_from_this<talk_to_svr> , public coroutine, boost::noncopyable { talk_to_svr(const std::string & username) : ... {} void start(ip::tcp::endpoint ep) { sock_.async_connect(ep, MEM_FN2(step,_1,0) ); } static ptr start(ip::tcp::endpoint ep, const std::string & username) { ptr new_(new talk_to_svr(username)); new_->start(ep); return new_; } void step(const error_code & err = error_code(), size_t bytes = 0) { reenter(this) { for (;;) { if ( !started_) { started_ = true; std::ostream out(&write_buf_); out << "login " << username_ << "\n"; } yield async_write(sock_, write_buf_, MEM_FN2(step,_1,_2) ); yield async_read_until( sock_,read_buf_,"\n", MEM_FN2(step,_1,_2)); yield service.post( MEM_FN(on_answer_from_server)); } } } void on_answer_from_server() { std::istream in(&read_buf_); std::string word; in >> word; if ( word == "login") on_login(); else if ( word == "ping") on_ping(); else if ( word == "clients") on_clients(); read_buf_.consume( read_buf_.size()); if (write_buf_.size() > 0) service.post( MEM_FN2(step,error_code(),0)); } ... private: ip::tcp::socket sock_; streambuf read_buf_, write_buf_; bool started_; std::string username_; deadline_timer timer_; };
      
      





接続を開始すると、サーバーに非同期で接続するstart()



関数が呼び出されます。 接続が確立されると、初めてstep()



入ります。 これは、ユーザー名でメッセージを送信するときです。

この後、 async_write



を使用してからasync_write



を使用してメッセージを処理します( on_answer_from_server



)。

on_answer_from_server



関数on_answer_from_server



on_answer_from_server



着信メッセージを処理します。 最初の単語を読み、対応する関数に送信し、メッセージの残りを無視します(いずれにしても):



 class talk_to_svr : ... { ... void on_login() { do_ask_clients(); } void on_ping() { std::istream in(&read_buf_); std::string answer; in >> answer; if ( answer == "client_list_changed") do_ask_clients(); else postpone_ping(); } void on_clients() { std::ostringstream clients; clients << &read_buf_; std::cout << username_ << ", new client list:" << clients. str(); postpone_ping(); } void do_ping() { std::ostream out(&write_buf_); out << "ping\n"; service.post( MEM_FN2(step,error_code(),0)); } void postpone_ping() { timer_.expires_from_now(boost::posix_time::millisec(rand() % 7000)); timer_.async_wait( MEM_FN(do_ping)); } void do_ask_clients() { std::ostream out(&write_buf_); out << "ask_clients\n"; } };
      
      





ランダムな瞬間にサーバーとの接続を確認する必要があるため、この例はもう少し複雑です。 これを行うには、初めて顧客リストを正常に要求した後、ping操作を延期します。 次に、サーバーからの各pingに対して、別のping操作を延期します。

これをすべて実行するには、次のコードスニペットを使用します。



 int main(int argc, char* argv[]) { ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001); talk_to_svr::start(ep, "John"); service.run(); }
      
      





コルーチンを使用して、コードを15行に減らし、さらに読みやすくしました。 ここでは、コルーチンのトピックについてはほとんど触れませんでした。 この問題の詳細については、このページにアクセスしてください



まとめ



Boost.AsioがSTLストリームとstreambufオブジェクトをどのように簡単に使用できるかを見ました。 また、コルーチンによってコードがよりコンパクトになり、理解が容易になることも確認しました。

次の章では、Asio vs. Boost.Asio、プログレッシブデバッグ、SSL、およびその他のプラットフォーム固有の機能などのトピックを扱います。



この記事のリソース: リンク



ご清聴ありがとうございました、すぐに会いましょう!



All Articles