「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、およびその他のプラットフォヌム固有の機胜などのトピックを扱いたす。

この蚘事のリ゜ヌス リンク

ご枅聎ありがずうございたした、すぐに䌚いたしょう

Source: https://habr.com/ru/post/J196888/


All Articles