この記事では、主にnodejs処理のtcp接続のコアプロセスを紹介します。この記事では、すべての人の研究や仕事のための特定の参照値を持つサンプルコードを通じて詳細に紹介します。それを必要とする友人は参照できます。 |
数日前、nodejsでのepollとリクエストの処理に関する知識を小さなパートナーと交換しました。今日は、nodejsでリクエストを処理するロジックについて簡単に説明します。リッスン機能から始めます。
int uv_tcp_listen(uv_tcp_t * tcp、int backlog、uv_connection_cb cb){ //リクエストを処理するための戦略を設定します。以下の分析を参照してください if(single_accept == -1){ const char * val = getenv( "UV_TCP_SINGLE_ACCEPT"); single_accept =(val!= NULL && atoi(val)!= 0); / *デフォルトでオフ。* / ) if(single_accept) tcp-> flags | = UV_HANDLE_TCP_SINGLE_ACCEPT; //バインドを実行するかフラグを設定する err = may_new_socket(tcp、 AF_INET、flags); //リッスンを開始 if(listen(tcp-> io_watcher.fd、backlog)) return UV__ERR(errno); //コールバックを設定 tcp-> connection_cb = cb; tcp-> flags | = UV_HANDLE_BOUND; // Set ioウォッチャーのコールバックは、 epollが接続の到着を監視するときに実行されます。tcp-> io_watcher.cb = uv__server_io; //オブザーバーキューを挿入します。現時点では、epollに追加されていませんステージで、オブザーバーキューをトラバースして処理します(epoll_ctl) uv__io_start(tcp-> loop、&tcp-> io_watcher、POLLIN); 0を返します。 }
createServerを使用すると、Libuvレイヤーが従来のネットワークプログラミングのロジックであることがわかります。この時点で私たちのサービスが開始されます。投票段階では、リスニングファイル記述子とコンテキスト(対象のイベント、コールバックなど)がepollに登録されます。通常、epollでブロックされます。では、この時点でtcp接続が確立されるとどうなりますか?Epollは、最初にイベントをトリガーしたfdをトラバースし、次にfdコンテキスト、つまりuvserver_ioでコールバックを実行します。uvserver_ioを見てみましょう。
void uv__server_io(uv_loop_t * loop、uv__io_t * w、unsigned int events){ //ループ処理、uv__stream_fd(stream)はサーバーに対応するfdです while(uv__stream_fd(stream)!= -1){ //クライアントを取得します終了通信のfdを受け入れると、このfdがサーバーのfdとは異なることがわかります err = uv__accept(uv__stream_fd(stream)); // uv__stream_fd(stream)に対応するfdは非ブロッキングです。このエラーを返すと、受け入れることができる接続がないこと。、直接返す if(err <0){ if(err == UV_EAGAIN || err == UV__ERR(EWOULDBLOCK)) return; } //記録する stream-> accepted_fd = err; / /コールバック stream-> connection_cb(stream、0);を実行します / * stream-> accepted_fdは-1であり、accepted_fdがコールバックconnection_cbで消費されたことを示します。 そうでない場合、サーバーのepollのfdの読み取りイベントが最初にキャンセルされます、消費後に登録されます。つまり、リクエストは処理されなくなります * / if( stream- > accepted_fd!= -1){ uv__io_stop(loop、&stream-> io_watcher、POLLIN); return; } / * ok、accepted_fdが消費されましたが、 設定されている場合、新しいfdを引き続き受け入れますかUV_HANDLE_TCP_SINGLE_ACCEPTは、一度に1つの接続が処理され、 しばらくスリープして、他のプロセスが受け入れる機会を与えます(マルチプロセスアーキテクチャの場合)。マルチプロセスアーキテクチャでない場合、これを再度設定する と、接続の処理に遅延が発生します * / if(stream-> type == UV_TCP && (stream- > flags&UV_HANDLE_TCP_SINGLE_ACCEPT)){ struct timespec timeout = {0、 1}; nanosleep(&timeout、NULL); } } }
uv__server_ioから、Libuvは常にループ内の新しいfdを受け入れ、コールバックを実行することがわかります。通常、コールバックはfdを消費し、以下同様にプロセスへの接続がなくなるまで続きます。次に、コールバックでfdがどのように消費されるかに焦点を当てましょう。ループの数が多いと時間がかかりすぎて、Libuvのイベントループがしばらくブロックされます。tcpコールバックは、c ++レイヤーのOnConnectionです。
//接続 テンプレートがある場合にコールバックがトリガーされ ますvoidConnectionWrap :: OnConnection(uv_stream_t * handle、 int status){ // Libuv構造に対応するc ++レイヤーオブジェクト WrapType *を取得します wrap_data = static_cast(handle-> data); CHECK_EQ( &wrap_data-> handle_、reinterpret_cast(handle)); Environment * env = wrap_data-> env(); HandleScope handle_scope(env-> isolate()); Context :: Scope context_scope(env-> context()); //および顧客は 、ローカルclient_handleを通信するオブジェクト側; IF(Status == 0){ //オブジェクトをインスタンス化してクライアントJavaScriptとハンドルを処理し ますローカルclient_obj を使用してjsオブジェクトのレイヤーを作成します; return; IF(WrapType :: Instantiate(env 、wrap_data、WrapType!:: SOCKET) .ToLocal(&client_obj)) wrap_data-> MakeCallback(env-> onconnection_string()、arraysize(argv)、argv); //クライアントのjavascriptオブジェクトをアンラップします 。WrapType* wrap; // jsレイヤーで使用されるオブジェクトclient_objに対応するc ++レイヤーオブジェクトをラップに 格納しますASSIGN_OR_RETURN_UNWRAP(&wrap、client_obj); //対応するハンドルを取得し ますuv_stream_t * client = reinterpret_cast (&wrap-> handle_); // handleaccpetからfdの1つを取得してクライアントに保存すると、クライアントはクライアントと通信できます if(uv_accept(handle、client)) return; client_handle = client_obj; } else { client_handle = Undefined(env-> isolate ()); } //コールバックjs、client_handleは新しいTCP ローカルの実行と同等ですargv [] = {Integer :: New(env-> isolate()、status)、client_handle}; }
コードは複雑に見えます。uv_acceptに焦点を合わせるだけです。uv_acceptのパラメーター。最初のパラメーターはサーバーに対応するハンドルで、2番目のパラメーターはクライアントと通信するオブジェクトです。
int uv_accept(uv_stream_t * server、uv_stream_t * client){ int err; スイッチ(クライアント- >型){ ケースUV_NAMED_PIPE: ケースUV_TCP: //把FD设置到クライアント中 ERR = uv__stream_open(クライアント、 サーバ- > accepted_fd、 UV_HANDLE_READABLE | UV_HANDLE_WRITABLE)。 ブレーク; // ... } client-> flags | = UV_HANDLE_BOUND; //¸记已经消费了fd サーバー-> accepted_fd = -1; エラーを返します。 }
uv_acceptは主に2つのロジックであり、クライアントと通信するfdをクライアントに設定し、それが消費されたことをマークします。これにより、前述のwhileループを駆動して実行を続行します。上位層の場合、クライアントでオブジェクトを取得します。これは、Libuv層の構造、c ++層のc ++オブジェクト、およびjs層のjsオブジェクトです。これらの3つは、カプセル化され、接続された層です。コアはLibuvのクライアント構造のfdであり、これはクライアントと通信するための基礎となるチケットです。jsレイヤーへの最後のコールバックは、net.jsのonconnectionを実行することです。onconnectionは、クライアントと通信するためのSocketオブジェクトをカプセル化します。これは、c ++レイヤーオブジェクトを保持し、c ++レイヤーオブジェクトはLibuv構造を保持し、Libuv構造はfdを保持します。
const socket = new Socket({ ハンドル:clientHandle、 allowHalfOpen:self.allowHalfOpen、 pauseOnCreate:self.pauseOnConnect、 読み取り可能:true、 書き込み可能:true }); const socket = new Socket({ ハンドル:clientHandle、 allowHalfOpen:self.allowHalfOpen、 pauseOnCreate:self.pauseOnConnect、 読み取り可能:true、 書き込み可能:true });
これまでのところ、tcp接続を処理するnodejsのコアプロセスに関するこの記事を紹介しました。ご支援いただきありがとうございます。