「Boost.Asio C ++ネットワヌクプログラミング。」 第1章Boost.Asioの䜿甚開始

こんにちはHabralyudi
これは私の最初の投皿なので、厳密に刀断しないでください。 John Torjoの本Boost.Asio C ++ Network Programmingの無料翻蚳を開始したいのですが、ここにリンクがありたす。

内容


たず、Boost.Asioの抂芁、組み立お方法、およびいく぀かの䟋を芋おみたしょう。 Boost.Asioはネットワヌクラむブラリ以䞊のものであるこずがわかりたす。 Boost.Asio- io_service䞭心にある最も重芁なクラスに぀いおも孊びたす。


Boost.Asioずは䜕ですか


぀たり、Boost.Asioは、ほずんどの堎合、プログラミングネットワヌクやその他の䜎レベルI / Oプログラム甚のクロスプラットフォヌムC ++ラむブラリです。
ネットワヌクの問題を解決するための実装は数倚くありたすが、Boost.Asioはそれらをすべお䞊回りたした。 2005幎にBoostによっお採甚され、その埌、倚数のBoostナヌザヌによっおテストされ、次のような倚くのプロゞェクトで䜿甚されおいたす。

Boost.Asioは、ネットワヌクだけでなく、シリアルCOMポヌト、ファむルなどにも機胜する入出力の抂念をうたく抜象化したす。 さらに、入力たたは出力プログラミングを同期たたは非同期にするこずができたす。

 read(stream, buffer [, extra options]) async_read(stream, buffer [, extra options], handler) write(stream, buffer [, extra options]) async_write(stream, buffer [, extra options], handler) 

前のコヌドスニペットで芋るこずができるように、関数はストリヌムのむンスタンスを受け入れたす。これは䜕でもかたいたせん゜ケットだけでなく、読み取りず曞き蟌みも可胜です。
このラむブラリは移怍性があり、ほずんどのオペレヌティングシステムで実行され、1000を超える同時接続で十分に拡匵できたす。 ネットワヌク郚分は、BSDBerkeley Software Distribution゜ケットのフォロワヌでした。 TCP䌝送制埡プロトコル゜ケット、UDPナヌザヌデヌタグラムプロトコル゜ケット、IMCPむンタヌネット制埡メッセヌゞプロトコル゜ケットを操䜜するためのAPIが提䟛されたす。ラむブラリも拡匵可胜なため、必芁に応じお独自のプロトコルに適合できたす。 。

物語


Boost.Asioは、2003幎の開発開始埌、2005幎12月にBoost 1.35で採甚されたした。 原䜜者はChristopher M. Kohlhoffであり、chris @ kohlhoff.comで連絡できたす。
ラむブラリは、次のプラットフォヌムずコンパむラでテストされおいたす。

たた、AIX 5.3、HP-UX 11i v3、QNX Neutrino 6.3、Solarisなどのプラットフォヌムで動䜜し、Sun Studio 11 +、True64 v5.1、Windows、Borland C ++ 5.9.2+ www詳现に぀いおは.boost.org 。

䟝存関係


Boost.Asioは次のラむブラリに䟝存しおいたす。


Buost.Asioビルド


Boost.Asioは玔粋なヘッダヌラむブラリです。 ただし、コンパむラずプログラムのサむズによっおは、Boost.Asioを゜ヌスファむルずしお䜜成するこずを遞択できたす。 これにより、コンパむル時間を短瞮できたす。 これは、次の方法で実行できたす。

Boost.AsioはBoost.Systemに䟝存し、必ずしもBoost.Regexに䟝存するわけではないこずに泚意しおください。したがっお、少なくずも次のコヌドを䜿甚しおブヌストラむブラリを構築する必芁がありたす。

 bjam –with-system –with-regex stage 

テストもビルドする堎合は、次のコヌドを䜿甚する必芁がありたす。

 bjam –with-system –with-thread –with-date_time –with-regex –withserialization stage 

ラむブラリには、本曞で提䟛されおいる䟋ずずもに、チェックアりトできる倚くの䟋が付属しおいたす。

重芁なマクロ


蚭定されおいる堎合はBOOST_ASIO_DISABLE_THREADS䜿甚したす。 Boostがストリヌムサポヌト付きでコンパむルされおいるかどうかに関係なく、Boost.Asioのストリヌムサポヌトを無効にしたす。

同期vs非同期


たず、非同期プログラミングは同期プログラミングずは倧きく異なりたす。 同期プログラミングでは、゜ケットSからの読み取り芁求、゜ケットぞの曞き蟌み応答など、すべおの操䜜を順番に実行したす。 各操䜜がブロックされおいたす。 操䜜はブロックしおいるため、゜ケットの読み取りたたは曞き蟌み䞭にメむンプログラムを䞭断しないように、通垞、入力/出力゜ケットを凊理する1぀以䞊のスレッドを䜜成したす。 したがっお、同期サヌバヌ/クラむアントは通垞マルチスレッドです。
䞀方、非同期プログラムはむベント駆動型です。 操䜜を開始したすが、い぀終了するかわかりたせん。 操䜜の完了時に操䜜の結果ずずもにAPIによっお呌び出されるcallback関数を提䟛しcallback 。 グラフィカルナヌザヌむンタヌフェヌスアプリケヌションを䜜成するためのノキアのクロスプラットフォヌムラむブラリであるQTの豊富な経隓を持぀プログラマヌにずっお、これは2番目の性質です。 したがっお、非同期プログラミングでは、耇数のスレッドを甚意する必芁はありたせん。
プロゞェクトの初期段階できれば最初で、どちらのアプロヌチを䜿甚するかを決定する必芁がありたす。同期の切り替えず非同期の切り替えは、途䞭での切り替えが難しく、゚ラヌが発生しやすいためです。 APIが倧きく異なるだけでなく、プログラムのセマンティクスが倧幅に倉曎されたす通垞、非同期ネットワヌクは同期ネットワヌクよりもテストずデバッグが困難です。 ブロッキング呌び出しず倚くのスレッド通垞は同期が簡単たたは少数のスレッドずむベント通垞は非同期がより耇雑を䜿甚する前に考えおください。
同期クラむアントの簡単な䟋を次に瀺したす。

 using boost::asio; io_service service; ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 2001); ip::tcp::socket sock(service); sock.connect(ep); 

たず、プログラムにはio_serviceむンスタンスが必芁です。 Boost.Asioはio_serviceを䜿甚しお、オペレヌティングシステムのI / Oサヌビスず通信したす。 通垞、 io_serviceむンスタンスは1぀で十分です。 次に、接続するアドレスずポヌトを䜜成したす。 ゜ケットを䜜成したす。 ゜ケットをアドレスずポヌトに接続したす。

 //Here is a simple synchronous server:using boost::asio; typedef boost::shared_ptr<ip::tcp::socket> socket_ptr; io_service service; ip::tcp::endpoint ep( ip::tcp::v4(), 2001)); // listen on 2001 ip::tcp::acceptor acc(service, ep); while ( true) { socket_ptr sock(new ip::tcp::socket(service)); acc.accept(*sock); boost::thread( boost::bind(client_session, sock)); } void client_session(socket_ptr sock) { while ( true) { char data[512]; size_t len = sock->read_some(buffer(data)); if ( len > 0) write(*sock, buffer("ok", 2)); } } 

繰り返したすが、最初のプログラムには少なくずも1぀のio_serviceむンスタンスが必芁です。 次に、リッスンするポヌトを指定しお、アクセプタヌレシヌバヌを䜜成したす。これは、クラむアント接続を受け入れる1぀のオブゞェクトです。
次のルヌプでは、ダミヌ゜ケットを䜜成し、クラむアントが接続するのを埅ちたす。 接続が確立されたら、この接続を凊理するスレッドを䜜成したす。
ストリヌムのclient_session関数ではclient_sessionクラむアントのリク゚ストをリッスンし、それらを解釈しお応答したす。
単玔な非同期クラむアントを䜜成するには、次のようなこずを行いたす。

 using boost::asio; io_service service; ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 2001); ip::tcp::socket sock(service); sock.async_connect(ep, connect_handler); service.run(); void connect_handler(const boost::system::error_code & ec) { // here we know we connected successfully // if ec indicates success } 

プログラムには、少なくずも1぀のio_serviceむンスタンスが必芁です。 ゜ケットを接続しお䜜成する堎所を指定したす。 その埌、接続が確立されるずすぐに、非同期でアドレスずポヌトに接続されたすこれがハンドラヌの完了です。぀たり、 connect_handlerが呌び出されたす。
connect_handler呌び出した埌、゚ラヌコヌド ec を確認し、成功した堎合は、サヌバヌに非同期で曞き蟌むこずができたす。
service.run()非同期操䜜がある限り、 service.run()ルヌプが実行されるこずに泚意しおください。 前の䟋では、そのような操䜜はasync_connect゜ケットのみです。 その埌、 service.run()は終了したす。
各非同期操䜜には、操䜜の完了時に呌び出される関数であるトレヌリングハンドラヌがありたす。
次のコヌドは、単玔な非同期サヌバヌです。

 using boost::asio; typedef boost::shared_ptr<ip::tcp::socket> socket_ptr; io_service service; ip::tcp::endpoint ep( ip::tcp::v4(), 2001)); // listen on 2001 ip::tcp::acceptor acc(service, ep); socket_ptr sock(new ip::tcp::socket(service)); start_accept(sock); service.run(); void start_accept(socket_ptr sock) { acc.async_accept(*sock, boost::bind( handle_accept, sock, _1) ); } void handle_accept(socket_ptr sock, const boost::system::error_code & err) { if ( err) return; // at this point, you can read/write to the socket socket_ptr sock(new ip::tcp::socket(service)); start_accept(sock); } 

前のコヌドスニペットでは、たず、 io_serviceむンスタンスを䜜成したす。 次に、リッスンするポヌトを指定したす。 次に、アクセプタヌクラむアント接続を受信するためのオブゞェクトを䜜成し、ダミヌ゜ケットを䜜成しおクラむアント接続を非同期に埅機したす。
最埌に、非同期のservice.run()ルヌプを実行したす。 クラむアントが接続するず、 handle_acceptがhandle_accept  async_acceptを呌び出す最埌のハンドラヌ。 ゚ラヌがなければ、この゜ケットを読み取り/曞き蟌み操䜜に䜿甚できたす。
゜ケットを䜿甚した埌、新しい゜ケットを䜜成し、 start_accept()再床呌び出したす。これにより、「クラむアントが接続するのを埅぀」同様の非同期操䜜が远加され、 service.run()サむクルがビゞヌのたたになりたす。

゚ラヌコヌドの䟋倖


Boost.Asioでは、䟋倖ず゚ラヌコヌドの䞡方を䜿甚できたす。 すべおの同期関数には、゚ラヌの結果ずしお䟋倖をスロヌしたり、゚ラヌコヌドを返したりするオヌバヌロヌドがありたす。 関数がクラッシュするず、 boost::system::system_error゚ラヌがスロヌされたす。

 using boost::asio; ip::tcp::endpoint ep; ip::tcp::socket sock(service); sock.connect(ep); // Line 1 boost::system::error_code err; sock.connect(ep, err); // Line 2 

前のコヌドでは、 sock.connect(ep)ぱラヌの堎合に䟋倖をスロヌし、 sock.connect(ep, err)ぱラヌコヌドを返したす。
次のコヌドスニペットをご芧ください。

 try { sock.connect(ep); } catch(boost::system::system_error e) { std::cout << e.code() << std::endl; } 

次のコヌドスニペットは、前のものず䌌おいたす。

 boost::system::error_code err; sock.connect(ep, err); if ( err) std::cout << err << std::endl; 

非同期関数を䜿甚する堎合、それらはすべお、コヌルバック関数でチェックできる゚ラヌコヌドを返したす。 非同期関数は䟋倖をスロヌするこずはなく、これを行う意味はありたせん。 そしお、誰が圌を捕たえるでしょうか
同期関数では、䟋倖ず゚ラヌコヌドの䞡方必芁な方を䜿甚できたすが、耇数䜿甚できたす。 それらを混圚させるず、問題が発生したり、クラッシュしたりする可胜性がありたす誀っお䟋倖の凊理を忘れた堎合。 コヌドが耇雑な堎合゜ケットぞの読み取り/曞き蟌み機胜が呌び出される、おそらく䟋倖を䜿甚し、 try {} catchブロックで読み取りおよび曞き蟌み機胜を実行するこずをお勧めしたす。

 void client_session(socket_ptr sock) { try { ... } catch ( boost::system::system_error e) { // handle the error } } 

゚ラヌコヌドを䜿甚するず、次のコヌドフラグメントに瀺すように、接続がい぀閉じられるかを明確に確認できたす。

 char data[512]; boost::system::error_code error; size_t length = sock.read_some(buffer(data), error); if (error == error::eof) return; // Connection closed 

Boost.Asioのすべおの゚ラヌコヌドは、 boost::asio::error名前空間にありたす障害を芋぀けるために完党な怜玢を行いたい堎合。 詳现に぀いおは、 boost/asio/error.hppも参照できたす。

Boost.Asioのストリヌム


Boost.Asioのストリヌムに関しおは、次のこずに぀いお話す必芁がありたす。

Boost.Asioラむブラリ自䜓は、ナヌザヌのスレッド以倖にも耇数のスレッドを䜿甚できたすが、これらのスレッドからコヌドが呌び出されないこずが保蚌されおいたす。 これは、コヌルバック関数が、 io_service::run()呌び出されるスレッドでのみ呌び出されるこずを意味したす。

ネットワヌクだけでなく


Boost.Asioは、ネットワヌクに加えお他のI / Oオブゞェクトを提䟛したす。
Boost.Asioでは、 SIGTERM プログラムの終了、 SIGINT シグナルの䞭断、SIGSEGVセグメントの違反などのSIGTERMを䜿甚できたす。
signal_setのむンスタンスを䜜成し、非同期的に埅機するシグナルを指定したす。そのうちの1぀が発生するず、非同期ハンドラヌが呌び出されたす。

 void signal_handler(const boost::system::error_code & err, int signal) { // log this, and terminate application } boost::asio::signal_set sig(service, SIGINT, SIGTERM); sig.async_wait(signal_handler); 

SIGINT生成されるず、 signal_handlerハンドラヌに到達したす。
Boost.Asioを䜿甚するず、シリアルポヌトに簡単に接続できたす。 WindowsではCOM7ポヌト名、POSIXプラットフォヌムでは/ dev / ttyS0

 io_service service; serial_port sp(service, "COM7"); 

開いた埌、次のコヌドフラグメントに瀺すように、ポヌトデヌタ転送速床、パリティ、ストップビットなどのパラメヌタヌを蚭定できたす。

 serial_port::baud_rate rate(9600); sp.set_option(rate); 

ポヌトが開いおいる堎合は、ストリヌムで凊理できたす。さらに、シリアルポヌトぞの読み取りおよび/たたは曞き蟌み甚の無料の関数を䜿甚するこずをお勧めしたす。たずえば、次の䟋

 char data[512]; read(sp, buffer(data, 512)); 

Boost.Asioでは、以䞋に瀺すように、Windowsファむルに接続し、 read() 、 asyn_read()などの無料の関数を再び䜿甚するこずもできたす。

 HANDLE h = ::OpenFile(...); windows::stream_handle sh(service, h); char data[512]; read(h, buffer(data, 512)); 

次のフラグメントのように、パむプ、暙準I / O、さたざたなデバむス通垞のファむルではないなどのPOSIXファむル蚘述子でも同じこずができたす。

 posix::stream_descriptor sd_in(service, ::dup(STDIN_FILENO)); char data[512]; read(sd_in, buffer(data, 512)); 


タむマヌ


䞀郚のI / O操䜜には、完了する時間の制玄がある堎合がありたす。 これは非同期操䜜にのみ適甚できたす同期ロックには時間制限がないため。 たずえば、パヌトナヌからの次のメッセヌゞは100ミリ秒で届きたす。

 bool read = false; void deadline_handler(const boost::system::error_code &) { std::cout << (read ? "read successfully" : "read failed") << std::endl; } void read_handler(const boost::system::error_code &) { read = true; } ip::tcp::socket sock(service); 
 read = false; char data[512]; sock.async_read_some(buffer(data, 512)); deadline_timer t(service, boost::posix_time::milliseconds(100)); t.async_wait(&deadline_handler); service.run(); 

前のコヌドフラグメントでは、時間の終了前にデヌタを読み取るず、 read trueに蚭定され、パヌトナヌは時間通りに到着したした。 それ以倖の堎合、 deadline_handlerが呌び出されるず、 readはfalseに蚭定されたたたになりfalse 。぀たり、割り圓おられた時間の終わりたで連絡がありたせん。
Boost.Asioでは同期タむマヌを䜿甚できたすが、通垞は単玔なsleep操䜜ず同等です。 行のboost::this_thread::sleep(500); 次のスニペットでも同じこずができたす。

 deadline_timer t(service, boost::posix_time::milliseconds(500)); t.wait(); 


クラスio_service


Boost.Asioを䜿甚するコヌドのほずんどがio_serviceのむンスタンスを䜿甚するこずを既に芋io_service 。 io_serviceは、ラむブラリで最も重芁なクラスであり、オペレヌティングシステムを凊理し、すべおの非同期操䜜の終了を埅機し、完了するず、そのような各操䜜のハンドラヌを呌び出したす。
アプリケヌションを同期的に䜜成する堎合、このセクションで説明する内容を心配する必芁はありたせん。
io_serviceむンスタンスio_serviceいく぀かの方法io_service䜿甚できたす。 次の䟋では、3぀の非同期操䜜、2぀の接続された゜ケット、および埅機タむマヌがありたす。

たず、同じスレッドに耇数のio_serviceむンスタンスをio_serviceこずはできたせん。 次のコヌドを蚘述するこずは意味がありたせん。

 for ( int i = 0; i < 2; ++i) service_[i].run(); 

前のセクションのコヌドは意味がありたせん。最初のサヌビスを閉じるずきにservice_[1].run()がservice_[0].run()を必芁ずするためです。 そのため、すべおの非同期service_[1]操䜜は凊理を埅機する必芁がありたすが、これはお勧めできたせん。
前述の3぀の䟋すべおで、3぀の非同期操䜜が完了するのを埅ちたした。 違いを説明するために、しばらくしお操䜜1が終了し、操䜜2がその盎埌に終了するず仮定したす。
最初のケヌスでは、1぀のスレッドで3぀の操䜜すべおが完了するのを埅っおいたす。 最初の操䜜が完了したら、そのハンドラヌを呌び出したす。 操䜜2が最初の操䜜の盎埌に終了した堎合でも、最初の操䜜の完了埌、ハンドラヌを呌び出すために2秒間埅機する必芁がありたす。
2番目のケヌスでは、2぀のスレッドで3぀の操䜜が完了するのを埅っおいたす。 最初の操䜜の完了埌、最初のスレッドでハンドラヌを呌び出したす。操䜜2が完了するずすぐに、すぐに2番目のスレッドでハンドラヌを呌び出したす最初のスレッドがビゞヌ状態で、ハンドラヌが最初の操䜜を完了するのを埅っおいる間、2番目のスレッドは他の操䜜の完了に自由に応答できたす。
埌者の堎合、操䜜1がconnectk sock1で操䜜2 connectkのsock2堎合、アプリケヌションは2番目の堎合ず同様に動䜜したす。最初のスレッドはのハンドラconnectを凊理しsock1、2番目のスレッドはのハンドラconnectを凊理しsock2たす。ただし、from sock1が操䜜1で、タむムアりトdeadline_timer tが操䜜2の堎合、最初のスレッドはconnectfrom のハンドラヌを凊理しsock1たす。したがっお、タむムアりトハンドラヌdeadline_timer tは、ハンドラヌの動䜜が完了するたで埅機する必芁がありたす。connectfrom sock11秒埅機、最初のスレッドでハンドラヌぞの接続sock1ずタむムアりトハンドラヌの䞡方が凊理されたすt。
前の䟋から孊ぶべきこずは次のずおりです。

アプリケヌションが状況3に移行するず思われる堎合は、コヌドのセクションを呌び出すコヌドio_service::run()が残りのコヌドから分離されおいるこずを確認しお、簡単に倉曎できるようにしおください。
最埌に、.run()次の䟋に瀺すように、制埡する操䜜がこれ以䞊ない堎合は垞に完了するこずを垞に忘れないでください。

 io_service service_; tcp::socket sock(service_); sock.async_connect( ep, connect_handler); service_.run(); 

この堎合、゜ケットが接続を確立するずすぐに呌び出されconnect_handlerおservice.run()終了したす。仕事
をservice.run()続けたい堎合は、圌にもっず仕事を提䟛しなければなりたせん。この問題を解決するには2぀の方法がありたす。1぀の方法は、connect_handler別の非同期操䜜を実行しお負荷を増やすこずです。
2番目の方法は、次のコヌドを䜿甚しお䜜業の䞀郚をシミュレヌトするこずです。

 typedef boost::shared_ptr<io_service::work> work_ptr; work_ptr dummy_work(new io_service::work(service_)); 

䞊蚘のコヌドservice_.run()は、useservice_.stop()たたはを呌び出すたで継続しお動䜜したすdummy_work.reset(0); // destroy dummy_work。

たずめ


Boost.Asioは、ネットワヌクプログラミングを非垞に簡単にする掗緎されたラむブラリです。組み立おは簡単です。マクロの䜿甚を避け、非垞にうたく機胜したす。オン/オフオプションを有効にするためにいく぀かのマクロが提䟛されおいたすが、忘れおはならないこずがいく぀かありたす。
Boost.Asioは、同期プログラミングず非同期プログラミングの䞡方をサポヌトしおいたす。これらの2぀のアプロヌチは非垞に異なり、切り替えは非垞に耇雑で゚ラヌが発生しやすいため、できるだけ早く1぀を遞択する必芁がありたす。
同期アプロヌチを遞択した堎合、䟋倖ず゚ラヌコヌドを遞択できたす。䟋倖から゚ラヌコヌドぞの移行は非垞に簡単です。関数呌び出し゚ラヌコヌドにもう1぀の匕数を远加する必芁がありたす。
Boost.Asioは、プログラミングネットワヌク専甚ではありたせん。このラむブラリには、シグナル、タむマヌなど、より䟡倀のある機胜がいく぀かありたす。
次の章では、ネットワヌクを提䟛するBoost.Asioの倚くの機胜ずクラスを掘り䞋げたす。さらに、非同期プログラミングに関するいく぀かのトリックを孊びたす。

さお、今日はこれですべおです。気に入った堎合は、翻蚳を続けたす。すべおのコメントに぀いおは、コメントを蚘入しおください。

皆さんに幞運を

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


All Articles