如何计算node.js套接字缓冲区以避免分配内存而从不使用它?

时间:2013-07-04 06:33:16

标签: node.js sockets memory tcp buffer

我正在使用node.js作为客户端之间的服务器来处理我的在线游戏。 客户端在下摆之间发送短消息[一条消息不应超过200字节]。 目前我希望单个客户端每秒发送[平均] 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-12MB)。 我相信这是由于缓冲区,并且因为它们分配了太多内存。

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

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

谢谢, 克里斯蒂安

编辑:

节点版本:

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

EDIT2:

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

1 个答案:

答案 0 :(得分:2)

内核有一个至少为8k的套接字接收缓冲区,用于处理套接字上的多个传入消息。您不需要缓冲已读取的消息,因此您的应用程序缓冲区不需要大于预期的最大消息。