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コンテナを使用してプロキシ転送とデータバックアップを実装する方法

推薦する

Vueカスケードドロップダウンボックスの設計と実装

目次1. データベース設計2. フロントエンドページ3. 完全なデモフロントエンド開発では、カスケー...

データベースクエリ、どのオブジェクトにどのフィールドが含まれているか、メソッドステートメント

データベースは、どのオブジェクトにどのフィールドが含まれているかを照会します。 *を選択 sysob...

Vueのトグルボタンをクリックしてボタンを有効にし、無効にします。

実装方法は3つのステップに分かれています。テンプレートに 2 つのボタンを設定し、v-if と v-...

タブバーのいくつかの実装方法(推奨)

タブ: カテゴリ + 説明タグバー: カテゴリ => ユーザーに現在地と目的地を知らせる1. ...

React Fiberの仕組みの詳細な説明

目次React Fiberとは何ですか?なぜReact Fiberなのか? React Fiberは...

CSS3はさまざまな境界効果を実現します

半透明の境界線結果: 実装コード: <div> 半透明の境界線が見えますか? </...

エラー mysql テーブル 'performance_schema...解決方法

テスト環境は、JDBCドライバを使用してMariaDB 5.7でセットアップされています。 <...

モバイルインターネット時代: レスポンシブウェブデザインが一般的なトレンドに

今はモバイルインターネットが急速に発展している時代です。スマートフォンやタブレットはますます普及し、...

DockerコンテナがSongtiなどのフォントを認識しない場合の解決策

問題の背景: docker を使用してプロジェクトをデプロイする場合、プロジェクト内で印刷コントロー...

JavaScript で文字列内の最長の単語を見つける 3 つの方法 (推奨)

この記事は、Free Code Camp の基本アルゴリズム スクリプト「文字列内の最長の単語を見つ...

HTML で入力プロンプトのテキスト スタイルを変更するためのサンプル コード

多くのウェブサイトでは、入力ボックスにヒントテキストが表示されています。入力ボックスにヒントテキスト...

nginx がどのようにして高いパフォーマンスとスケーラビリティを実現するのかを深く理解する

NGINX の全体的なアーキテクチャは、連携して動作する一連のプロセスによって特徴付けられます。メイ...

役に立つメタ設定方法(必読)

<meta name="viewport" content="...

CSS マージンの折りたたみの詳細な説明

前のこれは古くからある古典的な質問です。以前読者から質問があったので、ここでお答えします。簡単な例か...

MySQL 5.7.22 バイナリパッケージのインストールとインストール不要版 Windows 設定方法

次のコードは、MySQL 5.7.22 バイナリ パッケージのインストール方法を紹介しています。具体...