Node.js の TCP 接続処理のコア プロセス

Node.js の TCP 接続処理のコア プロセス

数日前、友人と Node.js の epoll とリクエスト処理に関する知識を交換しました。今日は、Node.js のリクエスト処理のロジックについて簡単に説明します。まず listen 関数から始めます。

int uv_tcp_listen(uv_tcp_t* tcp, int バックログ, uv_connection_cb cb) {
 // リクエストを処理するための戦略を設定します。以下の分析を参照してください。if (single_accept == -1) {
  const char* val = getenv("UV_TCP_SINGLE_ACCEPT");
  single_accept = (val != NULL && atoi(val) != 0); /* デフォルトではオフです。 */
 }
 (単一受け入れ)の場合
  tcp->フラグ |= UV_HANDLE_TCP_SINGLE_ACCEPT;
 // バインドを実行するかフラグを設定します。err = maybe_new_socket(tcp, AF_INET, flags);
 // リスニングを開始する if (listen(tcp->io_watcher.fd, backlog))
  UV__ERR(errno) を返します。
 // コールバックを設定します tcp->connection_cb = cb;
 tcp->フラグ |= UV_HANDLE_BOUND;
 // epoll が接続を監視するときに実行される io ウォッチャーのコールバックを設定します。tcp->io_watcher.cb = uv__server_io;
 // オブザーバー キューを挿入します。この時点では、epoll に追加されていません。poll io ステージは、処理のためにオブザーバー キューをトラバースします (epoll_ctl)
 uv__io_start(tcp->loop, &tcp->io_watcher, POLLIN);

 0を返します。
}

createServer を実行すると、Libuv レイヤーが従来のネットワーク プログラミング ロジックに従うことがわかります。この時点で弊社のサービスが開始されます。ポーリング IO フェーズでは、リスニング ファイル記述子とコンテキスト (関心のあるイベント、コールバックなど) が 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です
 uv__stream_fd(ストリーム) != -1 の場合{
  // accept を介してクライアントと通信するための fd を取得します。この fd はサーバーの fd と異なることがわかります。err = uv__accept(uv__stream_fd(stream));
  // uv__stream_fd(stream) に対応する fd は非ブロッキングです。このエラーを返すということは、受け入れ可能な接続がないことを意味します。直接 return if (err < 0) {
   エラー == UV_EAGAIN || エラー == UV__ERR(EWOULDBLOCK) の場合
    戻る;
  }
  //記録する stream->accepted_fd = err;
  // コールバックを実行します stream->connection_cb(stream, 0);
  /*
   stream->accepted_fd は -1 です。これは、accepted_fd がコールバック connection_cb で消費されたことを意味します。
   それ以外の場合は、まず epoll でサーバーの fd の読み取りイベントを登録解除し、消費を待ってから再度登録します。つまり、リクエストを処理しなくなります。*/
  ストリーム->accepted_fd != -1 の場合 {
   uv__io_stop(ループ、&stream->io_watcher、POLLIN);
   戻る;
  }
 /*
   わかりました。accepted_fd は消費されましたが、まだ新しい fd を受け入れますか?
   UV_HANDLE_TCP_SINGLE_ACCEPT が設定されている場合、一度に 1 つの接続のみが処理され、その後、他のプロセスが受け入れる機会を与えるためにしばらくスリープ状態になります (マルチプロセス アーキテクチャの場合)。マルチプロセスアーキテクチャでない場合は、これを再度設定します。
   これにより、接続処理が遅延します*/
  if (stream->type == UV_TCP &&
    (ストリーム->フラグ & UV_HANDLE_TCP_SINGLE_ACCEPT)) {
   構造体timespecタイムアウト = {0, 1};
   ナノスリープ(&timeout, NULL);
  }
 }
}

uv__server_io から、Libuv がループ内で継続的に新しい fd を受け入れ、コールバックを実行することがわかります。通常、コールバックは fd を消費し、処理する接続がなくなるまでサイクルが続きます。次に、コールバックで fd がどのように消費されるか、そしてループの数が多いと時間がかかりすぎて、Libuv イベント ループがしばらくブロックされるかどうかに注目しましょう。 tcp のコールバックは c++ レイヤーの OnConnection です。

//接続テンプレート <typename WrapType, typename UVType> がある場合にトリガーされるコールバック
void ConnectionWrap<WrapType, UVType>::OnConnection(uv_stream_t* ハンドル,
                          int ステータス) {
 // Libuv 構造体 WrapType* に対応する C++ レイヤー オブジェクトを取得します。 wrap_data = static_cast<WrapType*>(handle->data);
 CHECK_EQ(&wrap_data->handle_, reinterpret_cast<UVType*>(handle));

 環境* env = wrap_data->env();
 ハンドルスコープ handle_scope(env->isolate());
 コンテキスト::スコープ context_scope(env->context());

 //クライアントと通信するためのオブジェクト Local<Value> client_handle;

 (ステータス == 0)の場合{
  // クライアントの JavaScript オブジェクトとハンドルをインスタンス化します。
  // オブジェクトを使用して新しい js レイヤーを作成します Local<Object> client_obj;
  if (!WrapType::Instantiate(env, wrap_data, WrapType::SOCKET)
       .ToLocal(&client_obj))
   戻る;

  // クライアントの JavaScript オブジェクトをアンラップします。
  ラップタイプ* ラップ;
  // js レイヤーで使用されるオブジェクト client_obj に対応する c++ レイヤー オブジェクトを wrap に格納します。ASSIGN_OR_RETURN_UNWRAP(&wrap, client_obj);
  // 対応するハンドルを取得する
  uv_stream_t* クライアント = reinterpret_cast<uv_stream_t*>(&wrap->handle_);
  // handleAccept によって受信された fd の 1 つを取得してクライアントに保存し、クライアントがクライアントと通信できるようにします if (uv_accept(handle, client))
   戻る;
  クライアントハンドル = クライアントオブジェクト;
 } それ以外 {
  client_handle = 未定義(env->isolate());
 }
 // コールバック js、client_handle は js レイヤーで新しい TCP を実行することと同等です
 Local<Value> argv[] = { Integer::New(env->isolate(), status), client_handle };
 wrap_data->MakeCallback(env->onconnection_string(), arraysize(argv), argv);
}

コードは複雑に見えますが、uv_accept だけに注目する必要があります。 uv_accept の最初のパラメータはサーバーに対応するハンドルであり、2 番目のパラメータはクライアントとの通信を表すオブジェクトです。

int uv_accept(uv_stream_t* サーバー、uv_stream_t* クライアント) {
 整数エラー;

 スイッチ (クライアント->タイプ) {
  UV_NAMED_PIPEの場合:
  UV_TCPの場合:
   // fdをクライアントに設定 err = uv__stream_open(client,
              サーバー->accepted_fd、
              UV_HANDLE_READABLE または UV_HANDLE_WRITABLE のいずれかです。
   壊す;
 // ...
 }

 クライアント->フラグ |= 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ソケット = 新しいソケット({
  ハンドル: clientHandle、
  半開きを許可: self.半開きを許可、
  作成時に一時停止: 接続時に自己一時停止、
  読み取り可能: true、
  書き込み可能: true
 });

これで、nodejs の tcp 接続処理のコア プロセスに関するこの記事は終了です。nodejs の tcp 接続処理に関するより関連性の高いコンテンツについては、123WORDPRESS.COM で以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • Node.js を使用してコマンドライン ゲームを実装する方法
  • Nodejs は、複数の人が同時にオンラインでマウスを動かして小さなゲームを共有することを実現します。
  • Node.js を使用したマルチプレイヤー ゲーム サーバー エンジンの実装
  • Node.js リアルタイム マルチプレイヤー ゲーム フレームワーク
  • node.js はゲームのバックエンド開発に適していますか?
  • Nodejs でタイムドクローラーを実装する完全な例
  • NodeJSとブラウザにおけるこのキーワードの違い
  • ゲームの Node.JS バージョンを作成する方法

<<:  mysql data_dirの変更によって発生するエラー問題を解決する

>>:  Dockerコンテナを使用してプロキシ転送とデータバックアップを実装する方法

推薦する

HTML マウス CSS コントロール

一般的に、マウスは上向きの斜め矢印として表示され、テキストの上に移動すると垂直線になり、ハイパーリン...

ElementUIはドロップダウンオプションと複数選択ボックスのサンプルコードを実装します

目次ドロップダウン複数選択ボックスアップグレード - すべてのオプションを追加改訂と改善を求める製品...

Linux LVM 論理ボリューム構成プロセス (作成、増加、削減、削除、アンインストール) の詳細な説明

Linux LVM論理ボリューム構成プロセスの詳細な説明多くの Linux ユーザーは、オペレーティ...

Linux マルチスレッドにおけるフォークとミューテックス ロック プロセスの例

目次質問: 1. 最初の試み2. 合理的な分析3. 問題解決(1) pthread_join()の使...

jQuery はピッカーをシミュレートしてスライド選択効果を実現します

この記事では、スライド選択効果を実現するピッカーをシミュレートするjQueryの具体的なコードを参考...

MySQL パーティションテーブルの正しい使用方法

MySQL パーティションテーブルの概要数億、あるいは数十億ものレコードを格納するテーブルに遭遇する...

jQueryはすべてのショッピングカート機能を実装します

目次1. すべて選択2. 商品の数量を増やすか減らす3. 商品の小計を変更する4. 合計と合計額を計...

JavaScript は自由に移動するウィンドウのマウス制御を実装します

この記事では、フリーウィンドウのマウス制御を実現するためのJavaScriptの具体的なコードを参考...

Vue の proto ファイルの関数呼び出しのグラフィカルな説明

1. protoをコンパイルするすべての .proto ファイルを保存するために、src フォルダー...

データベース管理における 19 の MySQL 最適化方法

MySQL データベースを最適化すると、データベースの冗長性を削減できるだけでなく、データベースの実...

MySql で、存在しない場合は挿入し、存在する場合は更新する方法

まとめシナリオによっては、レコードがない場合は挿入し、レコードがある場合は更新するという要件がある場...

js はランダムロールコールを実装します

この記事では、ランダムロールコールを実装するためのjsの具体的なコードを参考までに共有します。具体的...

JavaScript ドキュメント オブジェクト モデル DOM

目次1. JavaScriptはページ内のすべてのHTML要素を変更できる1. IDでHTML要素を...

TypeScript 2.0 マーク付き共用体型の詳細な説明

目次タグ付きユニオン型を使用した支払い方法の構築タグ付きユニオン型を使用した Redux アクション...

MySQL でコミットされていないトランザクション情報を見つける方法

少し前に、「ORACLE でコミットされていないトランザクションの SQL ステートメントを見つける...