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

推薦する

Unicode の数学記号の概要

数学、物理学、および一部の科学技術分野で使用される特殊記号は多数あります。Unicode コードには...

Windows システム mysql5.7.18 インストール グラフィック チュートリアル

Windows システム向け MySQL インストール チュートリアルダウンロード1. https:...

FileZilla を使用して FTP ファイル サービスを素早く構築する方法

ファイルの保存とアクセスを容易にするために、FTPサービスが特別に構築されています。 FTP サーバ...

MySQL 8.0.15 winx64 圧縮パッケージのインストールと設定方法のグラフィックチュートリアル

この記事では、MySQL 8.0.15 winx64 圧縮パッケージのインストールと設定方法を参考ま...

Linuxファイルの基本属性の知識ポイントのまとめ

Linux システムは典型的なマルチユーザー システムです。異なるユーザーは異なる立場にあり、異なる...

Vueダイナミックフォームの詳細な応用

概要バックグラウンド管理システムには多くのフォーム要件があります。データをjson 形式で書き込み、...

MySQL コマンドラインでよく使われる 18 個のコマンド

日常的なウェブサイトの保守と管理では、多くの SQL ステートメントが使用されます。熟練して使用する...

MySQLをシンプルに学ぶ

序文データベースは常に私の弱点でした。自分の経験 (python+sqlalchemy) を組み合わ...

CSS3 のディスプレイのグリッドレイアウトとフレックスレイアウトの詳細な説明

Gird レイアウトは Flex レイアウトといくつかの類似点があり、どちらもコンテナーの内部項目を...

MySQL 8.0.11 圧縮版のインストールチュートリアル

この記事では、MySQL 8.0.11のインストールチュートリアルを参考までに紹介します。具体的な内...

CentOS8.0 で FTP サーバーをインストールして設定する方法

CentOS8.0-1905 のリリース後、FTP サーバーを CentOS の新しいバージョンに移...

Dockerfile をベースに Zabbix 監視システムのコード例を作成する

forループを使用してZabbixイメージをコンテナにインポートします。 n を `ls *.tar...

JavaScriptのクローン作成についての簡単な説明

目次1. 浅いクローニング2. ディープクローニング1. 浅いクローニング浅いクローンでは配列やオブ...

Dockerイメージサイズを最適化する一般的な方法

通常、私たちが構築する Docker イメージはサイズが大きく、多くのディスク領域を占有します。コン...

MySQL のデータの偶発的な削除の解決策と kill ステートメントの原則

mysql が誤ってデータを削除しました削除ステートメントを使用して誤ってデータ行を削除する誤ってデ...