Note that there are some explanatory texts on larger screens.

plurals
  1. POHow to calculate node.js socket buffer to avoid allocating memory and never using it?
    primarykey
    data
    text
    <p>I'm using node.js as a server between pairs of clients, to handle my online game. Clients send short messages between hem [one message should not exceed 200bytes]. Currently I expect single client to send [on average] 1 message per second [keeping in mind it can be 5 seconds of nothing and 5 messages one after another].</p> <p>I've downloaded a sample server using 'net' module and rewritten it to handle the messages the way I need them to be handled. Basically, for every connected socket, it creates a Buffer with size of 1024*8. Currently I'm testing my game with some bots, which simply connect, wait 3 seconds and disconnect. They only send 1 message. Nothing else happening.</p> <pre><code>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 &gt; (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("&lt;somthing&gt;")) != -1 &amp;&amp; (end = str.indexOf("&lt;/something&gt;")) != -1) { try { if (!&lt;some check to see if the message format is right&gt;) { sendMessage(socket, "&lt;error message to the client&gt;"); return; } &lt;storing info on the socket&gt; } catch(err) { sendMessage(socket, "&lt;error message to the client&gt;"); return; } socket.channel = &lt;channel&gt;; 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, "&lt;info message to the client&gt;"); for (var prop in waiting[socket.channel]) { if (waiting[socket.channel].hasOwnProperty(prop) &amp;&amp; waiting[socket.channel][prop].connection_id != socket.connection_id) { &lt;here I'll try to advertise this client among other clients&gt; sendMessage(waiting[socket.channel][prop], "&lt;info to other clients about new client&gt;"); } } } var time_to_exit = true; do{ // this is for a case when several messages arrived in buffer if ( (start = str.indexOf("&lt;some other format&gt;")) != -1 &amp;&amp; (end = str.indexOf("&lt;/some other format&gt;")) != -1 ) { var json = str.substr( start+19, end-(start+19) ); var jsono; try { jsono = JSON.parse(json); } catch(err) { sendMessage(socket, "&lt;parse error&gt;"); return; } if (&lt;message indicates two clients are going to play together&gt;) { if (waiting[socket.channel][jsono.other_client_id] &amp;&amp; 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, "&lt;start game with the other socket&gt;"); opponentSocket.opponentConnectionId = socket.connection_id; sendMessage(socket, "&lt;start game with the other socket&gt;"); socket.opponentConnectionId = jsono.other_client_id; } } else if (&lt;check if clients play together&gt;) { var opponentSocket = sockets[socket.channel][socket.opponentConnectionId]; if (&lt;some generic action between clients, just pass the message&gt;) { sendMessage(sockets[socket.channel][socket.opponentConnectionId], json); } else if (&lt;match is over&gt;) { if (&lt;match still in progress&gt;) { &lt;send some messages indicating who won, who lost&gt; } else { &lt;log an error&gt; } 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] &amp;&amp; waiting[socket.channel][socket.connection_id]) { delete waiting[socket.channel][socket.connection_id]; } var opponentSocket = sockets[socket.channel][socket.opponentConnectionId]; if (opponentSocket) { sendMessage(opponentSocket, "&lt;the other client has disconnected&gt;"); 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); </code></pre> <p>I've pasted the above code [very stripped version of the original] to give you and idea about how simple the server is. I've got an array of sockets, who joined the game and array of sockets on the waiting list, waiting for another client to play with. Nothing else is going on.</p> <p>Still the script is memory hungry - 5 hours of connecting and disconnecting gave me this:</p> <pre><code> 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 </code></pre> <p>I think this is way too much. I'm using nodetime.com free service at the moment to monitor the script, but none of the metrics would suggest the script gained so much memory (it starts with just 10-12MB). I believe this is due to the buffers, and because they allocate too much memory.</p> <p>I'm only wondering, if my assumptions regarding buffer size are correct. Should I adjust the buffer to reflect the amount of data I expect from the client? If I expect the client to send 5 messages with a very short time between them, 200 bytes max each, should I assume that 1024*3 would be enough?</p> <p>Or should I adjust buffer size according to the message size I expect, so if I'm sure the message will never go above 300 bytes, I should be fine with buffer size of 512?</p> <p>Thanks, Krystian</p> <p>EDIT:</p> <p>Node version:</p> <pre><code>$ node -v v0.10.5 $ npm -v 1.2.19 </code></pre> <p>EDIT2:</p> <p>I've tested the script with 400 connections connecting and disconnecting and memory usage dropped significantly to around 60MB. After changing the test setup back to 4 connections it went up again.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload