1

我使用 node.js 作为一对客户端之间的服务器来处理我的在线游戏。客户端之间发送短消息[一条消息不应超过200字节]。目前,我希望单个客户端每秒 [平均] 发送 1 条消息 [请记住,它可能是 5 秒什么都没有,然后是 5 条消息接连发送]。

我已经使用“net”模块下载了一个示例服务器,并对其进行了重写,以按照我需要的方式处理消息。基本上,对于每个连接的套接字,它都会创建一个大小为 1024*8 的 Buffer。目前我正在使用一些机器人测试我的游戏,这些机器人只需连接、等待 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-12MB)。我相信这是由于缓冲区,并且因为它们分配了太多内存。

我只是想知道,如果我对缓冲区大小的假设是正确的。我应该调整缓冲区以反映我期望从客户端获得的数据量吗?如果我希望客户端发送 5 条消息,它们之间的时间很短,每条最多 200 字节,我应该假设 1024*3 就足够了吗?

或者我应该根据我期望的消息大小调整缓冲区大小,所以如果我确定消息永远不会超过 300 字节,那么缓冲区大小为 512 应该没问题?

谢谢,克里斯蒂安

编辑:

节点版本:

$ node -v
v0.10.5
$ npm -v
1.2.19

编辑2:

我已经测试了 400 个连接和断开连接的脚本,内存使用量显着下降到 60MB 左右。将测试设置改回 4 个连接后,它再次上升。

4

1 回答 1

2

内核有一个至少 8k 的套接字接收缓冲区,它负责处理套接字上的多个传入消息。你不需要缓冲你已经读过的消息,所以你的应用程序缓冲区不需要比最大的预期消息大。

于 2013-07-06T01:30:51.537 回答