この記事は私が最近 Node.js を勉強しているときに思いついたアイデアで、皆さんと議論したいと思います。
Node.js 用の HTTP サーバー
Node.js を使用して http サービスを実装するのは非常に簡単です。最も簡単な例は、公式 Web サイトに示されているとおりです。
1. Node.js はシングルスレッドであるため、その堅牢性の保証には開発者にとって比較的高い要件があります。
2. サーバー上にはポート 80 を占有している他の http サービスが存在する可能性があり、ポート 80 以外の Web サービスは明らかにユーザーフレンドリーではありません。
3.Node.js は、ファイル IO 処理ではあまり利点がありません。たとえば、通常の Web サイトとして、画像と他のファイル リソースに同時に応答する必要がある場合があります。
4. 分散負荷シナリオも課題です。
したがって、Node.js を Web サービスとして使用することは、ゲーム サーバー インターフェイスやその他の同様のシナリオとして使用される可能性が高く、そのほとんどはユーザーによる直接アクセスを必要とせず、データ交換のみに使用されるサービスを扱います。 。
フロントエンド マシンとして Nginx に基づく Node.js Web サービス
上記の理由に基づいて、Node.js を使用して構築された Web サイト形式の製品の場合、従来の使用法は、Node.js Web サービスのフロントエンドに別の成熟した http サーバー (Nginx など) を配置することです。が最も一般的に使用されます。
次に、Nginx をリバース プロキシとして使用して、Node.js ベースの Web サービスにアクセスします。例:
location / {
proxy_pass http://127.0.0.1:1337;
}
場所 ~ .(gif|jpg|png|swf|ico|css|js)$ {
root /home/andy/wwwroot/yekai/static;
}
}
これにより、上で挙げた問題がより適切に解決されます。
FastCGI プロトコルを使用して通信します
しかし、上記のプロキシ方法にはいくつかの欠点もあります。
考えられるシナリオの 1 つは、基盤となる Node.js Web サービスへの直接の http アクセスを制御する必要があるということです。ただし、それを解決したい場合は、独自のサービスを使用するか、ファイアウォールのブロックに依存することもできます。
もう 1 つの理由は、プロキシ方式は結局のところネットワーク アプリケーション層でのソリューションであり、キープアライブの処理など、クライアント http とやり取りするデータを直接取得して処理するのはあまり便利ではないことです。 、トランク、さらにはクッキーまで。もちろん、これはプロキシサーバー自体の能力や機能の完成度にも関係します。
そこで、別の処理方法を試してみようと考えたのが、現在 php Web アプリケーションで一般的に使用されている FastCGI 方法でした。
FastCGI とは
Fast Common Gateway Interface/FastCGI は、対話型プログラムが Web サーバーと通信できるようにするプロトコルです。
FastCGI の背景は、CGI Web アプリケーションの代替として機能することです。最も明白な機能の 1 つは、FastCGI サービス プロセスを使用して、Web サーバーが環境変数とFastCGI プロセスなどのソケットを介したページ リクエストは、Unix ドメイン ソケットまたは TCP/IP 接続を介して Web サーバーに接続します。背景知識の詳細については、Wikipedia のエントリを参照してください。
Node.js の FastCGI 実装
理論的には、Node.js を使用して FastCGI プロセスを作成し、このプロセスに送信される Nginx のモニタリング リクエストを指定するだけです。 Nginx と Node.js はどちらもイベント駆動型のサービス モデルに基づいているため、「理論的には」自然なソリューションとなるはずです。以下、自分で実装してみましょう。
Node.js の net モジュールを使用してソケット サービスを作成できます。便宜上、unix ソケット メソッドを選択します。
Nginx 側の設定を少し変更します:
var server = net.createServer();
server.listen('/tmp/node_fcgi.sock');
server.on('connection', function(sock){
console.log('connection');
sock.on('data', function(data){
console.log(data);
});
});
次に、実行します (権限の理由により、Nginx スクリプトとノード スクリプトが相互権限を持つ同じユーザーまたはアカウントによって実行されていることを確認してください。そうしないと、sock ファイルの読み取りおよび書き込み時に権限の問題が発生します)。
node node_fcgi.js
ブラウザでアクセスすると、ノード スクリプトを実行している端末が次のようなデータ コンテンツを正常に受信していることがわかります:
FastCGI プロトコルの基礎
FastCGI レコードは、固定長のプレフィックスと、それに続く可変量のコンテンツとパディング バイトで構成されます。レコード構造は次のとおりです。
コードをコピー
contentData: 実際のコンテンツ データ。詳細は後ほど説明します。
paddingData: パディング データ、とにかくすべて 0 なので、無視してください。
具体的な構造と説明については、公式 Web サイトのドキュメント (http://www.fastcgi.com/devkit/doc/fcgi-spec.html#S3.3) を参照してください。
リクエストセクション
それは非常に簡単に思えます。一度解析してデータを取得するだけです。ただし、ここで落とし穴があります。ここで定義されるのは、バッファ全体の構造ではなく、データ単位 (レコード) の構造です。バッファ全体は 1 つのレコードと 1 つのレコードで構成されます。フロントエンド開発に慣れている人にとっては、最初は理解するのが難しいかもしれませんが、これは FastCGI プロトコルを理解するための基礎であり、後でさらに例を見ていきます。
したがって、各レコードを個別に解析し、前に取得したタイプに基づいてレコードを区別する必要があります。すべてのレコードを取得する簡単な関数を次に示します:
コードをコピー
var body = contentLength ? data.slice(end, contentLength) : null;
rcds.push([type, body, requestId]);
return argument.callee();
}
}
//使用
sock.on('data', function(data){
getRcds(data, function(rcds) ){
})();
}
注意: ここでは単に単一の処理であり、上流ファイルなどの場合、この関数が不適切な場合は、最優先で処理されます。同時に、requestId パラメータも省略されます (複数の場合は省略できません)。
では、タイプに応じてさまざまな認証を処理できます。タイプの定義は次のとおりです。
FCGI_PARAMS、FCGI_GET_VALUES、FCGI_GET_VALUES_RESULT はいずれも「コード名-値」タイプのデータであり、標準形式は次のとおりです。值的長さ、後続名、後続値の形式で送信され、127 文字以下の長さは 1 文字以内にコード化でき、それ以上の長さは常に 4 文字以内にコード化されます。長さを示すコード形式は、上位の 0 が 1 文字のコード形式を意味し、1 は 4 文字のコード形式を意味します。
对应の实现jsメソッド例:
= 0,params = {},
}
return params;
}
これにより、さまざまなパラメータと環境変数を簡単に取得できる方法が実現されます。
代码如下:
sock.on('data', function(data){
getRcds(data, function(rcds){
for (var i = 0, l = rcds.length; i
if(body && (type === TYPES.FCGI_PARAMS || type === TYPES.FCGI_GET_VALUES || type === TYPES.FCGI_GET_VALUES_RESULT)){
var params = parseParams(body);
FastCGI リクエスト部分の基本を理解しました。次に、レスポンス部分を実装して、最終的に簡単なエコー応答サービスを完成させます。
応答セクション
応答部分は比較的単純で、最も単純なケースでは、FCGI_STDOUT と FCGI_END_REQUEST の 2 つのレコードを送信するだけです。
記録されたエンティティの特定の内容については詳しく説明しません。コードを見てください:
関数buffer0(len){
return new Buffer((new Array(len 1)).join('u0000'));
};
function writeStdout(data){
var rcdStdoutHd = new Buffer(8),
contendLength = data.length,
paddingLength = 8 - contendLength % 8;
rcdStdoutHd[0] = 1;
rcdStdoutHd[1] = TYPES.FCGI_STDOUT;
rcdStdoutHd[2] = 0;
rcdStdoutHd[3] = 1;
rcdStdoutHd[4] = contendLength >> 8;
rcdStdoutHd[5] = contendLength;
rcdStdoutHd[6] = パディング長;
rcdStdoutHd[7] = 0;
return Buffer.concat([rcdStdoutHd, data,buffer0(paddingLength)]);
};
function writeHttpHead(){
return writeStdout(new Buffer("HTTP/1.1 200 OKrnContent-Type:text/html; charset=utf-8rnConnection: Closenrn"));
}
function writeHttpBody(bodyStr){
var bodyBuffer = [],
body = new Buffer(bodyStr);
for(var i = 0, l = body.length; i
関数 writeEnd(){
var rcdEndHd = new Buffer(8);
rcdEndHd[0] = 1;
rcdEndHd[2] = 0;
rcdEndHd[3] = 1;
rcdEndHd[4] = 0;
rcdEndHd[5] = 8;
rcdEndHd[6] = 0;
rcd EndHd[7] ] = 0;
return Buffer.concat([rcdEndHd,buffer0(8)]);
}
return function(data){
return Buffer.concat([writeHttpHead(), writeHttpBody(data), writeEnd()]);
};
最も単純なケースでは、これにより完全な応答を送信できます。最終的なコードを変更します:
コードをコピーします
比較テスト
最後に、考慮する必要がある問題は、この解決策が実現可能かどうかです。この問題に気づいた学生もいるかもしれないので、最初に簡単なストレス テストの結果を投稿します。
500 クライアント、20 秒実行。
速度 = 22131 ページ/分、63359 バイト/秒。
リクエスト: 6523 成功、854 失敗。
//プロキシ モード:
500 クライアント、10 秒実行。
速度 = 28752 ページ/分、73191 バイト/秒。
リクエスト: 3724 成功、1068 失敗。
500 クライアント、20 秒実行。
速度 = 26508 ページ/分、66267 バイト/秒。
リクエスト: 6716 成功、2120 失敗。
//Node.js サービス メソッドへの直接アクセス:
500 クライアント、10 秒実行。
速度 = 101154 ページ/分、264247 バイト/秒。
リクエスト: 15729 成功、1130 失敗。
500 クライアント、20 秒で実行。
速度 = 43791 ページ/分、115962 バイト/秒。
リクエスト: 13898 成功、699 失敗。
追記
引き続きプレイしたい場合は、この記事で実装したサンプルのソース コードを確認してください。ここ 2 日間でプロトコルの仕様を勉強しましたが、それは難しいことではありません。
同時に、私は再び uWSGI をプレイする予定ですが、公式によると、v8 はすでにそれを直接サポートする準備ができているとのことです。
ゲームはとても簡単なので、間違いがあれば修正してください。