オンラインゲームを処理するために、クライアントのペア間のサーバーとして node.js を使用しています。クライアントは、裾の間で短いメッセージを送信します [1 つのメッセージは 200 バイトを超えてはなりません]。現在、私は単一のクライアントが [平均して] 1 秒あたり 1 メッセージを送信することを期待しています [5 秒間何も表示されず、5 つのメッセージが次々と送信される可能性があることを念頭に置いてください]。
「net」モジュールを使用してサンプルサーバーをダウンロードし、必要な方法でメッセージを処理するように書き直しました。基本的に、接続されたソケットごとに、サイズが 1024*8 のバッファーが作成されます。現在、接続して 3 秒待ってから切断するだけのボットでゲームをテストしています。彼らは1つのメッセージしか送信しません。他に何も起こっていません。
function sendMessage(socket, message) {
socket.write(message);
}
server.on('connection', function(socket) {
socket.setNoDelay(true);
socket.connection_id = require('crypto').createHash('sha1').update( 'krystian' + Date.now() + Math.random() ).digest('hex') ; // unique sha1 hash generation
socket.channel = '';
socket.matchInProgress = false
socket.resultAnnounced = false;
socket.buffer = new Buffer(cfg.buffer_size);
socket.buffer.len = 0; // due to Buffer's nature we have to keep track of buffer contents ourself
_log('New client: ' + socket.remoteAddress +':'+ socket.remotePort);
socket.on('data', function(data_raw) { // data_raw is an instance of Buffer as well
if (data_raw.length > (cfg.buffer_size - socket.buffer.len)) {
_log("Message doesn't fit the buffer. Adjust the buffer size in configuration");
socket.buffer.len = 0; // trimming buffer
return false;
}
socket.buffer.len += data_raw.copy(socket.buffer, socket.buffer.len); // keeping track of how much data we have in buffer
var str, start, end
, conn_id = socket.connection_id;
str = socket.buffer.slice(0,socket.buffer.len).toString();
if ( (start = str.indexOf("<somthing>")) != -1 && (end = str.indexOf("</something>")) != -1) {
try {
if (!<some check to see if the message format is right>) {
sendMessage(socket, "<error message to the client>");
return;
}
<storing info on the socket>
} catch(err) {
sendMessage(socket, "<error message to the client>");
return;
}
socket.channel = <channel>;
str = str.substr(end + 11);
socket.buffer.len = socket.buffer.write(str, 0);
sockets[socket.channel] = sockets[socket.channel] || {}; // hashmap of sockets subscribed to the same channel
sockets[socket.channel][conn_id] = socket;
waiting[socket.channel] = waiting[socket.channel] || {};
waiting[socket.channel][conn_id] = socket;
sendMessage(socket, "<info message to the client>");
for (var prop in waiting[socket.channel]) {
if (waiting[socket.channel].hasOwnProperty(prop) && waiting[socket.channel][prop].connection_id != socket.connection_id) {
<here I'll try to advertise this client among other clients>
sendMessage(waiting[socket.channel][prop], "<info to other clients about new client>");
}
}
}
var time_to_exit = true;
do{ // this is for a case when several messages arrived in buffer
if ( (start = str.indexOf("<some other format>")) != -1 && (end = str.indexOf("</some other format>")) != -1 ) {
var json = str.substr( start+19, end-(start+19) );
var jsono;
try {
jsono = JSON.parse(json);
} catch(err) {
sendMessage(socket, "<parse error>");
return;
}
if (<message indicates two clients are going to play together>) {
if (waiting[socket.channel][jsono.other_client_id] && waiting[socket.channel][socket.connection_id]) {
delete waiting[socket.channel][jsono.other_client_id];
delete waiting[socket.channel][socket.connection_id];
var opponentSocket = sockets[socket.channel][jsono.other_client_id];
sendMessage(opponentSocket, "<start game with the other socket>");
opponentSocket.opponentConnectionId = socket.connection_id;
sendMessage(socket, "<start game with the other socket>");
socket.opponentConnectionId = jsono.other_client_id;
}
} else if (<check if clients play together>) {
var opponentSocket = sockets[socket.channel][socket.opponentConnectionId];
if (<some generic action between clients, just pass the message>) {
sendMessage(sockets[socket.channel][socket.opponentConnectionId], json);
} else if (<match is over>) {
if (<match still in progress>) {
<send some messages indicating who won, who lost>
} else {
<log an error>
}
delete sockets[socket.channel][opponentSocket.connection_id];
delete sockets[socket.channel][socket.connection_id];
}
}
str = str.substr(end + 20); // cut the message and remove the precedant part of the buffer since it can't be processed
socket.buffer.len = socket.buffer.write(str, 0);
time_to_exit = false;
} else { time_to_exit = true; } // if no json data found in buffer - then it is time to exit this loop
} while ( !time_to_exit );
}); // end of socket.on 'data'
socket.on('close', function(){ // we need to cut out closed socket from array of client socket connections
if (!socket.channel || !sockets[socket.channel]) return;
if (waiting[socket.channel] && waiting[socket.channel][socket.connection_id]) {
delete waiting[socket.channel][socket.connection_id];
}
var opponentSocket = sockets[socket.channel][socket.opponentConnectionId];
if (opponentSocket) {
sendMessage(opponentSocket, "<the other client has disconnected>");
delete sockets[socket.channel][socket.opponentConnectionId];
}
delete sockets[socket.channel][socket.connection_id];
_log(socket.connection_id + " has been disconnected from channel " + socket.channel);
}); // end of socket.on 'close'
}); // end of server.on 'connection'
server.on('listening', function(){ console.log('Listening on ' + server.address().address +':'+ server.address().port); });
server.listen(cfg.port);
上記のコード [オリジナルの非常に削除されたバージョン] を貼り付けて、サーバーがいかに単純であるかを理解してもらいました。ゲームに参加したソケットの配列と、待機リストにあるソケットの配列があり、別のクライアントがプレイするのを待っています。他に何も起こっていません。
それでもスクリプトはメモリを大量に消費します - 接続と切断に 5 時間費やした結果、次のようになりました。
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
31461 ec2-user 20 0 995m 91m 7188 S 0.7 15.4 1:29.07 node
これは多すぎると思います。現在、nodetime.com の無料サービスを使用してスクリプトを監視していますが、スクリプトが大量のメモリを獲得したことを示すメトリックはありません (わずか 10 ~ 12 MB から始まります)。これはバッファが原因であり、メモリを割り当てすぎているためだと思います。
バッファサイズに関する私の仮定が正しいかどうか、私は疑問に思っています。クライアントから期待されるデータ量を反映するようにバッファを調整する必要がありますか? クライアントがそれぞれ最大 200 バイトの非常に短い時間で 5 つのメッセージを送信すると予想される場合、1024*3 で十分であると想定する必要がありますか?
または、予想されるメッセージ サイズに応じてバッファー サイズを調整する必要があります。メッセージが 300 バイトを超えないことが確実な場合は、512 のバッファー サイズで問題ないはずですか?
ありがとう、クリスチャン
編集:
ノードのバージョン:
$ node -v
v0.10.5
$ npm -v
1.2.19
EDIT2:
400 の接続を接続および切断してスクリプトをテストしたところ、メモリ使用量は約 60MB に大幅に減少しました。テスト設定を 4 接続に戻すと、再び上昇しました。