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

推薦する

JavaScript タイマーの詳細

目次1. 簡単な紹介2. 間隔を設定する2.1 説明2.2 パラメータ2.3 戻り値2.4 使用法3...

VMware で Nginx+KeepAlived クラスタ デュアルアクティブ アーキテクチャを展開する際の問題と解決策

序文負荷分散には nginx を使用します。アーキテクチャのフロントエンドまたは中間層として、トラフ...

MySQLプロセスを安全かつ適切にシャットダウンする方法

序文この記事では、mysqld プロセスをシャットダウンするプロセスと、MySQL インスタンスを安...

MySQLログシステムの使い方に関する簡単なチュートリアル

目次序文1. エラーログ2. バイナリログ1. バイナリログを有効にする2. バイナリログ形式3. ...

docker createコマンドの使用方法

docker create コマンドは、イメージに基づいてコンテナを作成できます。このコマンドの効果...

Typescript の as、疑問符、感嘆符の詳細な説明

1. asキーワードはアサーションを示すTypescript では、アサーションを表現する方法が 2...

Nginx のパラメータをオンにして Web パフォーマンスを 3 倍向上させる方法

1. 遭遇したいくつかの問題2008 年にパフォーマンス テストを行っていたとき、パフォーマンス テ...

この記事ではSQL CASE WHENの使い方を詳しく説明します

目次シンプルな CASEWHEN 関数:これは、CASEWHEN 条件式関数を使用するのと同じです。...

Vueプロジェクトをパッケージ化してリリースする手順

目次1. 開発環境から本番環境への移行2. 統一されたリクエストパスを設定する3. パッケージ化コマ...

Linux システムで MySQL データベースにリモート接続する方法のチュートリアル

序文最近、職場でこの要件に遭遇し、リモート接続を確立するのに 1 時間以上かかりました。ローカル コ...

無効と読み取り専用で入力を読み取り専用に設定する

読み取り専用入力を実現するには、無効と読み取り専用の 2 つの方法があります。当然、どちらの結果も読...

Angularコンポーネントのライフサイクルの詳しい説明(パート2)

目次1. ビューフック1. ngAfterViewInit および ngAfterViewCheck...

VMware 構成 VMnet8 ネットワーク方法の手順

目次1. はじめに2. 設定手順1. はじめに1. NAT モード (VMnet8) は、仮想マシン...

インスタンス化されたオブジェクトパラメータによるMySQLクエリ例の説明

この記事では、オブジェクト パラメータをインスタンス化して MySQL でデータをクエリする方法を紹...