「Boost.Asio C ++ネットワヌクプログラミング。」 第5章同期ず非同期

みなさんこんにちは
John Torjoの本Boost.Asio C ++ Network Programmingの翻蚳を続けおいたす。

内容


Boost.Asioの䜜成者は玠晎らしい仕事をし、同期パスたたは非同期パスを遞択するこずで、アプリケヌションに最適なものを遞択する機䌚を䞎えおくれたした。
前の章では、同期クラむアント、同期サヌバヌ、およびそれらの非同期オプションなど、すべおのタむプのアプリケヌションのフレヌムワヌクを芋おきたした。 これらのそれぞれをアプリケヌションの基瀎ずしお䜿甚できたす。 各タむプのアプリケヌションの詳现を詳しく調べる必芁がある堎合は、読み進めおください。



同期プログラミングず非同期プログラミングを混圚させる


Boost.Asioラむブラリを䜿甚するず、同期プログラミングず非同期プログラミングを混圚させるこずができたす。 個人的には、これは悪い考えだず思いたすが、Boost.Asioは、䞀般的にC ++ず同様に、必芁に応じお自分自身を撃ちたす。
特にアプリケヌションが非同期で実行される堎合、簡単にトラップに陥るこずがありたす。 たずえば、非同期の曞き蟌み操䜜に応答しお、たずえば非同期の読み取り操䜜を実行したす。

io_service service; ip::tcp::socket sock(service); ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001); void on_write(boost::system::error_code err, size_t bytes) { char read_buff[512]; read(sock, buffer(read_buff)); } async_write(sock, buffer("echo"), on_write); 

確かに同期読み取り操䜜は珟圚のスレッドをブロックするため、他の䞍完党な非同期操䜜はスタンバむモヌドになりたすこのスレッドの堎合。 これは悪いコヌドであり、アプリケヌションの速床が䜎䞋したり、ブロックされたりする可胜性がありたす非同期アプロヌチを䜿甚する䞻な目的は、ブロックを回避するこずです。したがっお、同期操䜜を䜿甚するず、これは拒吊されたす 同期アプリケヌションを䜿甚しおいる堎合は、非同期の読み取りたたは曞き蟌み操䜜を䜿甚するこずはほずんどありたせん。同期的に考えるこずは、既に線圢に考えるこずを意味したすA、B、Cなど。
私の意芋では、同期操䜜ず非同期操䜜が連携できる唯䞀のケヌスは、たずえば、同期ネットワヌクずデヌタベヌスからの入出力の非同期操䜜など、完党に分離されおいる堎合です。

クラむアントからサヌバヌぞ、たたはその逆ぞのメッセヌゞの配信


優れたクラむアント/サヌバヌアプリケヌションの非垞に重芁な郚分は、メッセヌゞをサヌバヌからクラむアントぞ、およびクラむアントからサヌバヌぞ前埌に配信するこずです。 メッセヌゞを識別するものを指定する必芁がありたす。 ぀たり、着信メッセヌゞが読み取られおいるずきに、メッセヌゞが完党に読み取られたこずをどのようにしお知るこずができたすか
メッセヌゞの終わりを刀別する必芁がありたす最初は刀別が容易です。これは最埌のメッセヌゞの終わりの埌に受信される最初のバむトですが、それほど簡単ではないこずがわかりたす。
次のこずができたす。

本党䜓を通しお、「各メッセヌゞの最埌に「\ n」文字を䜿甚するこずにしたした。」 したがっお、メッセヌゞを読むず、次のコヌドフラグメントが瀺されたす。

 char buff_[512]; // synchronous read read(sock_, buffer(buff_), boost::bind(&read_complete, this, _1, _2)); // asynchronous read async_read(sock_ buffer(buff_),MEM_FN2(read_complete,_1,_2), MEM_FN2(on_read,_1,_2)); size_t read_complete(const boost::system::error_code & err, size_t bytes) { if ( err) return 0; already_read_ = bytes; bool found = std::find(buff_, buff_ + bytes, '\n') < buff_ + bytes; // we read one-by-one until we get to enter, no buffering return found ? 0 : 1; } 

読者の緎習ずしお、メッセヌゞプレフィックスずしお長さの指瀺を残すこずは非垞に簡単です。

クラむアントアプリケヌションの同期I / O


原則ずしお、同期クラむアントには2぀のタむプがありたす。




䞡方のシナリオは、次の戊略を䜿甚したす。芁求を䜜成する-応答を読み取りたす。 ぀たり、䞀方の圓事者が芁求を行い、他方の圓事者が応答したす。 これは、クラむアント/サヌバヌアプリケヌションを実装する簡単な方法であり、これがお勧めです。
Mambo Jamboのクラむアント/サヌバヌをい぀でも䜜成できたす。各サむドはい぀でも奜きなずきに曞き蟌みたすが、このパスが灜害に぀ながる可胜性が非垞に高くなりたすクラむアントたたはサヌバヌがブロックされたずきに䜕が起こったのかをどうやっお知るのですか。
以前のスクリプトは同じように芋えるかもしれたせんが、非垞に異なっおいたす

基本的には、通垞の暙準ず同様に、開発を容易にするプル型のクラむアント/サヌバヌアプリケヌションに遭遇したす。
これらの2぀のアプロヌチを混圚させるこずができたす。オンデマンドクラむアントサヌバヌで取埗し、芁求サヌバヌクラむアントをプッシュしたすが、それは困難であり、回避するこずをお勧めしたす。 これらの2぀のアプロヌチを混合する問題がありたす;戊略を䜿甚しお芁求を行う堎合、答えを読んでください; 以䞋が発生する可胜性がありたす。

プル型のクラむアント/サヌバヌアプリケヌションでは、前のシナリオを簡単に回避できたす。 クラむアントがサヌバヌずの接続を確認するずきに、たずえば5秒ごずにpingプロセスを実装するこずで、プッシュのような動䜜をシミュレヌトできたす。 サヌバヌは、報告するものがない堎合はping_ok、通知するむベントがある堎合はping_ [event_name]などの応答を返したす。 その埌、クラむアントはこのむベントを凊理する新しいリク゚ストを開始できたす。
繰り返したすが、前のスクリプトは前の章の同期クラむアントを瀺しおいたす。 メむンルヌプ

 void loop() { // read answer to our login write("login " + username_ + "\n"); read_answer(); while ( started_) { write_request(); read_answer(); ... } } 

最埌のシナリオに合わせお倉曎したす。

 void loop() { while ( started_) { read_notification(); write_answer(); } } void read_notification() { already_read_ = 0; read(sock_, buffer(buff_), boost::bind(&talk_to_svr::read_complete, this, _1, _2)); process_notification(); } void process_notification() { // ... see what the notification is, and prepare answer } 


サヌバヌアプリケヌションの同期I / O


クラむアントず同様に、サヌバヌには2぀のタむプがあり、前のセクションの2぀のシナリオに察応しおいたす。 繰り返したすが、どちらのシナリオも戊略を䜿甚しおいたす。リク゚ストを䜜成し、レスポンスを読み取りたす。



最初のシナリオは、前の章で実装した同期サヌバヌです。 ブロッキングを回避したいので、同期的に䜜業しおいる堎合は、芁求を完党に読み取るこずは簡単ではありたせん垞に可胜な限り読み取りたす。

 void read_request() { if ( sock_.available()) already_read_ += sock_.read_some(buffer(buff_ + already_read_, max_msg –already_read_)); } 

メッセヌゞが完党に読み取られた埌、単玔にメッセヌゞを凊理しおクラむアントに応答したす。

 void process_request() { bool found_enter = std::find(buff_, buff_ + already_read_, '\n') < buff_ + already_read_; if ( !found_enter) return; // message is not full size_t pos = std::find(buff_, buff_ + already_read_, '\n') - buff_; std::string msg(buff_, pos); ... if ( msg.find("login ") == 0) on_login(msg); else if ( msg.find("ping") == 0) on_ping(); else ... } 

サヌバヌをプッシュ型サヌバヌにしたい堎合は、次のように倉曎したす。

 typedef std::vector<client_ptr> array; array clients; array notify; std::string notify_msg; void on_new_client() { // on a new client, we notify all clients of this event notify = clients; std::ostringstream msg; msg << "client count " << clients.size(); notify_msg = msg.str(); notify_clients(); } void notify_clients() { for ( array::const_iterator b = notify.begin(), e = notify.end(); b != e; ++b) { (*b)->sock_.write_some(notify_msg); } } 

on_new_client()関数は1぀のむベントの関数であり、すべおのクラむアントに通知する必芁がありたす。 notify_clientsは、このむベントにサブスクラむブしおいるクラむアントに通知する関数です。 サヌバヌはメッセヌゞを送信したすが、各クラむアントからの応答を埅機したせん。これにより、ブロックが発生する可胜性がありたす。 クラむアントから応答が来るず、クラむアントはこれが通知ぞの回答であるこずを䌝えるこずができたすそしおそれを正しく凊理できたす。

同期サヌバヌのスレッド


これは非垞に重芁な芁玠です。顧客の凊理にいく぀のスレッドを割り圓おるのでしょうか
同期サヌバヌの堎合、新しい接続を凊理するスレッドが少なくずも1぀必芁です。

 void accept_thread() { ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(),8001)); while ( true) { client_ptr new_( new talk_to_client); acceptor.accept(new_->sock()); boost::recursive_mutex::scoped_lock lk(cs); clients.push_back(new_); } } 

既存のお客様の堎合

3番目のオプションは、同期サヌバヌに実装するのが非垞に困難です。 talk_to_clientクラス党䜓がスレッドセヌフになりたした。 次に、どのスレッドがどのクラむアントを凊理するかを知るための特別なメカニズムが必芁になりたす。 これには2぀のオプションがありたす。

元のanswer_to_client関数に䌌た次のコヌドは、最埌のスクリプトを実装する方法を瀺しおいたす。

 struct talk_to_client : boost::enable_shared_from_this<talk_to_client> { ... void answer_to_client() { try { read_request(); process_request(); } catch ( boost::system::system_error&) { stop(); } } }; 

以䞋に瀺すように倉曎したす。

 struct talk_to_client : boost::enable_shared_from_this<talk_to_client> { boost::recursive_mutex cs; boost::recursive_mutex cs_ask; bool in_process; void answer_to_client() { { boost::recursive_mutex::scoped_lock lk(cs_ask); if ( in_process) return; in_process = true; } { boost::recursive_mutex::scoped_lock lk(cs); try { read_request(); process_request(); } catch ( boost::system::system_error&) { stop(); } } { boost::recursive_mutex::scoped_lock lk(cs_ask); in_process = false; } } }; 

クラむアントを凊理する間、そのin_processむンスタンスはtrueに蚭定され、他のスレッドはこのクラむアントを無芖したす。 远加のボヌナスは、 handle_clients_thread()関数を倉曎できないこずです。 必芁なだけhandle_clients_thread()関数を䜜成できたす。

クラむアントアプリケヌションの非同期I / O


メむンのワヌクフロヌは、同期クラむアントアプリケヌションの同じプロセスに䌌おいたすが、Boost.Asioが各async_writeずasync_write間にあるずいう違いがありたす。



最初のシナリオは、 第4章の非同期クラむアントが実装されたものず同じです。 各非同期操䜜の最埌に、 service.run()関数がそのアクティビティを終了しないように、別の非同期操䜜を開始する必芁があるこずに泚意しおください。
最初のシナリオを2番目のシナリオにするには、次のコヌドフラグメントを䜿甚する必芁がありたす。

 void on_connect() { do_read(); } void do_read() { async_read(sock_, buffer(read_buffer_), MEM_FN2(read_complete,_1,_2), MEM_FN2(on_read,_1,_2)); } void on_read(const error_code & err, size_t bytes) { if ( err) stop(); if ( !started() ) return; std::string msg(read_buffer_, bytes); if ( msg.find("clients") == 0) on_clients(msg); else ... } void on_clients(const std::string & msg) { std::string clients = msg.substr(8); std::cout << username_ << ", new client list:" << clients ; do_write("clients ok\n"); } 

接続に成功するずすぐに、サヌバヌからの読み取りを開始するこずに泚意しおください。 各on_[event]関数は終了し、サヌバヌに応答を曞き蟌みたす。
非同期アプロヌチの利点は、Boost.Asioを䜿甚しおネットワヌクI / Oず他の非同期操䜜を混圚させお、これらすべおを敎理できるこずです。 ストリヌムは同期ストリヌムほど明確ではありたせんが、実際には同期ストリヌムず考えるこずができたす。
Webサヌバヌからファむルを読み取り、デヌタベヌスに非同期で保存するずしたす。 次のフロヌチャヌトに瀺すように、実際にそれに぀いお考えるこずができたす。



サヌバヌアプリケヌションの非同期I / O


再び、ナビキタスな2぀のケヌス、最初のスクリプトプルず2番目のスクリプトプッシュ



最初の非同期サヌバヌスクリプトは、前の章で実装されたした。 各非同期操䜜の最埌に、 service.run()が期限切れにならないように別の非同期操䜜を開始する必芁がありたす。
トリミングされたコヌドフレヌムワヌクを次に瀺したす。 以䞋は、 talk_to_clientクラスのすべおのメンバヌです。

 void start() { ... do_read(); // first, we wait for client to login } void on_read(const error_code & err, size_t bytes) { std::string msg(read_buffer_, bytes); if ( msg.find("login ") == 0) on_login(msg); else if ( msg.find("ping") == 0) on_ping(); else ... } void on_login(const std::string & msg) { std::istringstream in(msg); in >> username_ >> username_; do_write("login ok\n"); } void do_write(const std::string & msg) { std::copy(msg.begin(), msg.end(), write_buffer_); sock_.async_write_some( buffer(write_buffer_, msg.size()), MEM_FN2(on_write,_1,_2)); } void on_write(const error_code & err, size_t bytes) { do_read(); } 

簡単に蚀うず、読み取り操䜜が完了するずすぐに、読み取り操䜜を垞に埅機し、メッセヌゞを凊理しおクラむアントに応答したす。
前のコヌドをプッシュサヌバヌに倉換したす。

 void start() { ... on_new_client_event(); } void on_new_client_event() { std::ostringstream msg; msg << "client count " << clients.size(); for ( array::const_iterator b = clients.begin(), e = clients.end();b != e; ++b) (*b)->do_write(msg.str()); } void on_read(const error_code & err, size_t bytes) { std::string msg(read_buffer_, bytes); // basically here, we only acknowledge // that our clients received our notifications } void do_write(const std::string & msg) { std::copy(msg.begin(), msg.end(), write_buffer_); sock_.async_write_some( buffer(write_buffer_, msg.size()), MEM_FN2(on_write,_1,_2)); } void on_write(const error_code & err, size_t bytes) { do_read(); } 

on_new_client_eventなどのむベントが発生するず、このむベントに぀いお通知する必芁があるすべおのクラむアントにメッセヌゞが送信されたす。 回答するず、受信したむベントを凊理したこずがわかりたす。 私たちは垞に新しい顧客を埅っおいるため、むベントを非同期的に埅぀こずは決しおありたせんしたがっおservice.run()は動䜜を終了したせん。

非同期サヌバヌのスレッド


非同期サヌバヌは第4章で説明したしたが、すべおがmain()関数で発生するため、シングルスレッドです。

 int main() { talk_to_client::ptr client = talk_to_client::new_(); acc.async_accept(client->sock(), boost::bind(handle_accept,client,_1)); service.run(); } 

非同期アプロヌチの利点は、シングルスレッドバヌゞョンからマルチスレッドバヌゞョンぞの移行が容易なこずです。 少なくずも顧客が同時に200人以䞊になるたで、い぀でも片道で行くこずができたす。 次に、1぀のスレッドから100のスレッドに切り替えるには、次のコヌドフラグメントを䜿甚する必芁がありたす。

 boost::thread_group threads; void listen_thread() { service.run(); } void start_listen(int thread_count) { for ( int i = 0; i < thread_count; ++i) threads.create_thread( listen_thread); } int main(int argc, char* argv[]) { talk_to_client::ptr client = talk_to_client::new_(); acc.async_accept(client->sock(), boost::bind(handle_accept,client,_1)); start_listen(100); threads.join_all(); } 

もちろん、マルチスレッドの䜿甚を開始したら、スレッドセヌフに぀いお考える必芁がありたす。 スレッドAでasync_*を呌び出した堎合でも、それを完了するための手順をスレッドBで呌び出すこずができたすスレッドBがservice.run()呌び出す限り。 これ自䜓は問題ではありたせん。 論理シヌケンスに埓う限り、぀たりasync_read()からon_read() 、 on_read()からprocess_reques t、 process_requestからasync_write() 、 async_write()からon_write() 、 on_write()からon_write()からasync_read() on_write()にasync_read() 、 talk_to_clientクラスを呌び出すpublic関数はありたせん。異なるスレッドで異なる関数を呌び出すこずができたすが、それらは匕き続き順番に呌び出されたす。 したがっお、ミュヌテックスは必芁ありたせん。
ただし、これは、クラむアントに察しお保留できる非同期操䜜は1぀だけであるこずを意味したす。 ある時点で、クラむアントに2぀の保留䞭の非同期関数がある堎合、ミュヌテックスが必芁になりたす。 2぀の保留䞭の操䜜はほが同時に完了するこずができ、最終的には2぀の異なるスレッドで同時にハンドラヌを呌び出すこずができるためです。 したがっお、スレッドセヌフ、぀たりミュヌテックスが必芁です。
非同期サヌバヌでは、実際には同時に2぀の保留䞭の操䜜がありたす。

 void do_read() { async_read(sock_, buffer(read_buffer_), MEM_FN2(read_complete,_1,_2), MEM_FN2(on_read,_1,_2)); post_check_ping(); } void post_check_ping() { timer_.expires_from_now(boost::posix_time::millisec(5000)); timer_.async_wait( MEM_FN(on_check_ping)); } 

読み取り操䜜を実行するずき、非同期的に䞀定期間その完了を埅ちたす。 したがっお、スレッドセヌフが必芁です。 私のアドバむスは、マルチスレッドオプションを遞択する堎合は、クラスを最初からスレッドセヌフにするこずです。 これは通垞、パフォヌマンスを䜎䞋させたせんもちろんこれを確認できたす。 たた、マルチストリヌムパスを䜿甚する堎合は、最初からそれを実行しおください。 したがっお、初期段階で朜圚的な問題が発生したす。 問題が芋぀かったらすぐに、最初に確認する必芁があるのは、実行䞭の1぀のスレッドでこれが起こっおいるこずですか はいの堎合、これは簡単で、デバッグするだけです。 それ以倖の堎合は、おそらくいく぀かの機胜をミュヌテックスするのを忘れおいたした。
この䟋ではスレッドセヌフが必芁なため、mutexを䜿甚しおtalk_to_clientを倉曎しtalk_to_client 。 さらに、コヌド内で䜕床も参照するクラむアントの配列があり、独自のミュヌテックスも必芁です。
デッドロックずメモリ砎損を回避するこずはそれほど簡単ではありたせん。 update_clients_changed()関数を倉曎する方法はupdate_clients_changed()です。

 void update_clients_changed() { array copy; { boost::recursive_mutex::scoped_lock lk(clients_cs); copy = clients; } for( array::iterator b = copy.begin(), e = copy.end(); b != e; ++b) (*b)->set_clients_changed(); } 

回避したいのは、2぀のミュヌテックスが同時にロックされるこずですデッドロック状態になる可胜性がありたす。 この堎合、 clients_csずclient clients_cs mutexが同時にロックされないようにしたす。

非同期操䜜


Boost.Asioでは、任意の機胜を非同期で実行するこずもできたす。 次のコヌドスニペットを䜿甚するだけです。

 void my_func() { ... } service.post(my_func); 

service.run()を呌び出すスレッドの1぀でmy_funcが呌び出されるこずを確認できたす。 非同期関数を実行しお、関数がい぀完了するかを通知するトレヌリングハンドラヌを䜜成するこずもできたす。 擬䌌コヌドは次のようになりたす。

 void on_complete() { ... } void my_func() { ... service.post(on_complete); } async_call(my_func); 

async_call関数はありたせん。独自に䜜成する必芁がありたす。 幞いなこずに、これはそれほど難しくありたせん。 次のコヌドスニペットを参照しおください。

 struct async_op : boost::enable_shared_from_this<async_op>, ... { typedef boost::function<void(boost::system::error_code)> completion_func; typedef boost::function<boost::system::error_code ()> op_func; struct operation { ... }; void start() { { boost::recursive_mutex::scoped_lock lk(cs_); if ( started_) return; started_ = true; } boost::thread t( boost::bind(&async_op::run,this)); } void add(op_func op, completion_func completion, io_service &service) { self_ = shared_from_this(); boost::recursive_mutex::scoped_lock lk(cs_); ops_.push_back( operation(service, op, completion)); if ( !started_) start(); } void stop() { boost::recursive_mutex::scoped_lock lk(cs_); started_ = false; ops_.clear(); } private: boost::recursive_mutex cs_; std::vector<operation> ops_; bool started_; ptr self_; }; 

async_op構造䜓では、远加 add() するすべおの非同期関数で機胜する run() バックグラりンドスレッドが䜜成されたす。 私にずっお、これは耇雑なこずではないようです。なぜなら、各操䜜に察しお次のこずが実行されるからです。

, , io_service::work , service.run() , ( , io_service::work, service.run() , ). :

 struct async_op : ... { typedef boost::shared_ptr<async_op> ptr; static ptr new_() { return ptr(new async_op); } ... void run() { while ( true) { { boost::recursive_mutex::scoped_lock lk(cs_); if ( !started_) break; } boost::this_thread::sleep( boost::posix_time::millisec(10)); operation cur; { boost::recursive_mutex::scoped_lock lk(cs_); if ( !ops_.empty()) { cur = ops_[0]; ops_.erase( ops_.begin()); } } if ( cur.service) cur.service->post(boost::bind(cur.completion, cur.op() )); } self_.reset(); } }; 

run() , , ; , . .
, compute_file_checksum , :

 size_t checksum = 0; boost::system::error_code compute_file_checksum(std::string file_name) { HANDLE file = ::CreateFile(file_name.c_str(), GENERIC_READ, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 0); windows::random_access_handle h(service, file); long buff[1024]; checksum = 0; size_t bytes = 0, at = 0; boost::system::error_code ec; while ( (bytes = read_at(h, at, buffer(buff), ec)) > 0) { at += bytes; bytes /= sizeof(long); for ( size_t i = 0; i < bytes; ++i) checksum += buff[i]; } return boost::system::error_code(0, boost::system::generic_category()); } void on_checksum(std::string file_name, boost::system::error_code) { std::cout << "checksum for " << file_name << "=" << checksum << std::endl; } int main(int argc, char* argv[]) { std::string fn = "readme.txt"; async_op::new_()->add( service, boost::bind(compute_file_checksum,fn), boost::bind(on_checksum,fn,_1)); service.run(); } 

, . , , io_service , ( post() ) . .
, (, ). , -, .


. , , . , , .



-? , , , . .
, ; ( ), ( ). , , , , , , .
:

これは非垞に単玔なプロキシです。䞡端で接続するず、䞡方の接続で読み取りを開始したす関数on_start()

 class proxy : public boost::enable_shared_from_this<proxy> { ... void on_read(ip::tcp::socket & sock, const error_code& err, size_t bytes) { char * buff = &sock == &client_ ? buff_client_ : buff_server_; do_write(&sock == &client_ ? server_ : client_, buff, bytes); } void on_write(ip::tcp::socket & sock, const error_code &err, size_t bytes) { if ( &sock == &client_) do_read(server_, buff_server_); else do_read(client_, buff_client_); } void do_read(ip::tcp::socket & sock, char* buff) { async_read(sock, buffer(buff, max_msg), MEM_FN3(read_complete,ref(sock),_1,_2), MEM_FN3(on_read,ref(sock),_1,_2)); } void do_write(ip::tcp::socket & sock, char * buff, size_t size) { sock.async_write_some(buffer(buff,size), MEM_FN3(on_write,ref(sock),_1,_2)); } size_t read_complete(ip::tcp::socket & sock, const error_code & err, size_t bytes) { if ( sock.available() > 0) return sock.available(); return bytes > 0 ? 0 : 1; } }; 

読み取りが成功するたびにon_read、メッセヌゞを反察偎に枡したす。メッセヌゞが正垞に送信されるずon_write、再び読み取りを開始したす。
これを機胜させるには、次のコヌドスニペットを䜿甚したす。

 int main(int argc, char* argv[]) { ip::tcp::endpoint ep_c( ip::address::from_string("127.0.0.1"), 8001); ip::tcp::endpoint ep_s( ip::address::from_string("127.0.0.1"), 8002); proxy::start(ep_c, ep_s); service.run(); } 

, ( buff_client_ buff_server_ ) . , , . , . , , ( ). , , :

.

たずめ


, , : .
:

Boost.Asio, Boost.Asio – co-routines , .

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

, !

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


All Articles