Node JS : socket.io - handling multiple connection - node.js

I am a newbie in using socket.io in Node JS - however I have coded a client (web page) / server program to render some statistics data at client side.
The requirement is like – to render a box statistics (out of many). Since the user can open multiple browser windows - we have a scenario where one box data can be requested by many times:
http://www.tool.com?boxname=box1
As such I want to achieve spawning one job for multiple requests for same box. Below is the logic I have implemented to meet the requirement:
Client Side:
Establishes a connection to server creating a data channel:
socket.on(boxname.toLowerCase(), function (data){
}
So whenever there is a data in boxname event I receive, parse the data and render it.
Server Side
First call an expect script to copy a script to run within an infinite loop on a box.
On successful copying of the script in the box - invoke another expect script to start its execution:
exports.ServiceMonitor = function(boxName, res) {
Step 1.
I need to copy a script as many times request comes else I would not be able to enter ‘exit’ event of this spawned process where I start the script execution as mentioned below:
var child = spawn('./scripts/copyscriptonbox.sh' , [boxName.toLowerCase(), getURL(boxName)]);
In the later part of the code I keep adding box names to boxnames variable which is declared global. So on a new boxname request - I search for number of occurrences of boxname in boxnames variable. If the count is 1 or more it would mean to me that a script is already running on that box:
var noofbox = boxnames.split(boxName.toLowerCase()).length - 1;
child.on('exit', function (code) {
logger.info('child stream exit: ' + code);
if(code == 0)
{
boxNames += boxName.toLowerCase();
logger.info('Box name added: ' + boxNames);
res.render('boxpage', {}); //render a web page
io.sockets.on('connection', function (socket) {
logger.info('Connected to box :' + boxName);
if(noofbox <= 0)
schild = spawn('./scripts/boxmon.sh', [boxName.toLowerCase(), getURL(boxName)]);
schild.on('exit', function (code) {
logger.info('schild stream exit.');
});
schild.stderr.on('data', function (data) {
logger.info('stderr: ' + data);
});
schild.stdout.on('data', function (data) {
//generate response
});
socket.on('disconnect',function(){logger.info('Disconnect Killed');ServiceMonitorEnd(boxName.toLowerCase());});
});
}
});
}
The issue is that if in two browser window I access URL : www.tool.com?boxname=box1 - first time I get the log only once (Connected to box : box1) but second time I get the same logs 2 times where as I was expecting it to be one time - I mean as many request comes in after the first one the logs gets printed that many times – if 5 then log gets printed for 1(first time)+2 (second time)+3(third time)+4 (fourth time)+5 (fifth time)? I understand that when ‘connection’ event is called for x times then it enters that many times for each connection.
How can I make the 'connection' event on the socket.io once for each request only?

Use socket.once instead of socket.on, it is another EventEmitter like .on, that is emitted only once, the first time. Check the docs. But remember after that any such events will not be received by the client.

Related

socket.io how to send multiple messages sequentially?

I'm using socket.io like this
Client:
socket.on('response', function(i){
console.log(i);
});
socket.emit('request', whateverdata);
Server:
socket.on('request', function(whateverdata){
for (i=0; i<10000; i++){
console.log(i);
socket.emit('response', i);
}
console.log("done!");
});
I need output like this when putting the two terminals side by side:
Server Client
0 0
1 1
. (etc) .
. .
9998 9998
9999 9999
done!
But instead I am getting this:
Server Client
0
1
. (etc)
.
9998
9999
done!
0
1
.
. (etc)
9998
9999
Why?
Shouldn't Socket.IO / Node emit the message immediately, not wait for the loop to complete before emitting any of them?
Notes:
The for loop is very long and computationally slow.
This question is referring to the socket.io library, not websockets in general.
Due to latency, waiting for confirmation from the client before sending each response is not possible
The order that the messages are received is not important, only that they are received as quickly as possible
The server emits them all in a loop and it takes a small bit of time for them to get to the client and get processed by the client in another process. This should not be surprising.
It is also possible that the single-threaded nature of Javascript in node.js prevents the emits from actually getting sent until your Javascript loop finishes. That would take detailed examination of socket.io code to know for sure if that is an issue. As I said before if you want to 1,1 then 2,2 then 3,3 instead of 1,2,3 sent, then 1,2,3 received you have to write code to force that.
If you want the client to receive the first before the server sends the 2nd, then you have to make the client send a response to the first and have the server not send the 2nd until it receives the response from the first. This is all async networking. You don't control the order of events in different processes unless you write specific code to force a particular sequence.
Also, how do you have client and server in the same console anyway? Unless you are writing out precise timestamps, you wouldn't be able to tell exactly what event came before the other in two separate processes.
One thing you could try is to send 10, then do a setTimeout(fn, 1) to send the next 10 and so on. That would give JS a chance to breathe and perhaps process some other events that are waiting for you to finish to allow the packets to get sent.
There's another networking issue too. By default TCP tries to batch up your sends (at the lowest TCP level). Each time you send, it sets a short timer and doesn't actually send until that timer fires. If more data arrives before the timer fires, it just adds that data to the "pending" packet and sets the timer again. This is referred to as the Nagle's algorithm. You can disable this "feature" on a per-socket basis with socket.setNoDelay(). You have to call that on the actual TCP socket.
I am seeing some discussion that Nagle's algorithm may already be turned off for socket.io (by default). Not sure yet.
In stepping through the process of socket.io's .emit(), there are some cases where the socket is marked as not yet writable. In those cases, the packets are added to a buffer and will be processed "later" on some future tick of the event loop. I cannot see exactly what puts the socket temporarily in this state, but I've definitely seen it happen in the debugger. When it's that way, a tight loop of .emit() will just buffer and won't send until you let other events in the event loop process. This is why doing setTimeout(fn, 0) every so often to keep sending will then let the prior packets process. There's some other event that needs to get processed before socket.io makes the socket writable again.
The issue occurs in the flush() method in engine.io (the transport layer for socket.io). Here's the code for .flush():
Socket.prototype.flush = function () {
if ('closed' !== this.readyState &&
this.transport.writable &&
this.writeBuffer.length) {
debug('flushing buffer to transport');
this.emit('flush', this.writeBuffer);
this.server.emit('flush', this, this.writeBuffer);
var wbuf = this.writeBuffer;
this.writeBuffer = [];
if (!this.transport.supportsFraming) {
this.sentCallbackFn.push(this.packetsFn);
} else {
this.sentCallbackFn.push.apply(this.sentCallbackFn, this.packetsFn);
}
this.packetsFn = [];
this.transport.send(wbuf);
this.emit('drain');
this.server.emit('drain', this);
}
};
What happens sometimes is that this.transport.writable is false. And, when that happens, it does not send the data yet. It will be sent on some future tick of the event loop.
From what I can tell, it looks like the issue may be here in the WebSocket code:
WebSocket.prototype.send = function (packets) {
var self = this;
for (var i = 0; i < packets.length; i++) {
var packet = packets[i];
parser.encodePacket(packet, self.supportsBinary, send);
}
function send (data) {
debug('writing "%s"', data);
// always creates a new object since ws modifies it
var opts = {};
if (packet.options) {
opts.compress = packet.options.compress;
}
if (self.perMessageDeflate) {
var len = 'string' === typeof data ? Buffer.byteLength(data) : data.length;
if (len < self.perMessageDeflate.threshold) {
opts.compress = false;
}
}
self.writable = false;
self.socket.send(data, opts, onEnd);
}
function onEnd (err) {
if (err) return self.onError('write error', err.stack);
self.writable = true;
self.emit('drain');
}
};
Where you can see that the .writable property is set to false when some data is sent until it gets confirmation that the data has been written. So, when rapidly sending data in a loop, it may not be letting the event come through that signals that the data has been successfully sent. When you do a setTimeout() to let some things in the event loop get processed that confirmation event comes through and the .writable property gets set to true again so data can again be sent immediately.
To be honest, socket.io is built of so many abstract layers across dozens of modules that it's very difficult code to debug or analyze on GitHub so it's hard to be sure of the exact explanation. I did definitely see the .writable flag as false in the debugger which did cause a delay so this seems like a plausible explanation to me. I hope this helps.

node process can not find setTimeout object in subsequent requests

I am trying to clear timeout set using setTimeout method by node process, in subsequent requests (using express). So, basically, I set timeout when our live stream event starts (get notified by webhook) and aim to stop this for guest users after one hour. One hour is being calculated via setTimeout, which works fine so far. However, if event gets stopped before one hour, I need to clear the timeout. I am trying to use clearTimeOut but it just can't find same variable.
// Event starts
var setTimeoutIds = {};
var val = req.body.eventId;
setTimeoutIds[val] = setTimeout(function() {
req.app.io.emit('disable_for_guest',req.body);
live_events.update({event_id:req.body.eventId},{guest_visibility:false},function(err,data){
//All ok
});
}, disable_after_milliseconds);
console.log(setTimeoutIds);
req.app.io.emit('session_started',req.body);
When event ends:
try{
var event_id = req.body.eventId;
clearTimeout(setTimeoutIds[event_id]);
delete setTimeoutIds[event_id];
}catch(e){
console.log('Event ID could not be removed' + e);
}
req.app.io.emit('event_ended',req.body);
Output :
Output
You are defining setTimeoutIds in the scope of the handler. You must define it at module level.
var setTimeoutIds = {};
router.post('/webhook', function(req, res) {
...
That makes the variable available until the next restart of the server.
Note: this approach only works as long as you only have a single server with a single node process serving your application. Once you go multi-process and/or multi-server, you need a completely different approach.

how to avoid countdown timer reset on page refresh , node js / socket io

server.js
io.on('connection', function (socket) {
var addedUser = false;
socket.on('setTimer', function(data) {
timer.setEndTime(data.time);
socket.broadcast.emit('currentEndTime', {time: timer.getEndTime() });
});
});
client.js
$(function() {
var timer = new Timer(),
socket = io.connect('http://localhost:3000');
socket.on('currentEndTime', function (data) {
//this is the full date time in ms.
timer.setEndTimeFromServer(data.time);
});
set = setInterval(function(){
$('.time').trigger('click');
clearInterval(set);
},100);
$('.time').on('click', function(e) {
e.stopPropagation();
var time = $(this).text() * 1000;
timer.setEndTime(time);
timer.timeRemaining();
socket.emit('setTimer', { time: time });
});
});
Hi there, i am trying to integrate countdown timer for node js/socket io application. Timer works fine, but how do i avoid timer reset on new socket connection/page refresh. Thank You
This is because of two things:
Your socket goes away when you hit the refresh button and a new one is created and thus your connection to the server (and hence the timer value) goes away when you hit the refresh button.
When you load the page first, you don't get the current value of the timer and print it on your page to start everything.
You can have each individual timer stored in a key value storage. You can use the IP Address as key and timer value as value or something like that. This way, you can retrieve the current value of the timer for the user when user connects to the server and continue counting down.
If you use the socket ID as key, it will do you no good except to pointlessly populate a key value storage and spike up your memory usage. Use something more persistent, such as IP Address, username, e-mail or something of that sort as key and current value of timer as value.
Also, your current solution is flawed because it faces the "refresh lag". When you hit the refresh button, you stop counting for the amount of time that is spent on re-rendering of your web page. If it's nothing more than a handful of milliseconds, you're fine; but as long as it goes to a few hundreds of milliseconds, this will make actual difference. Do the count down at the server side. Just notify the client at the set intervals. This will increase the server workload, but at least you're not bound to having bad timers.
If you're going to have a single countdown, like "x days, x hours, x minutes, x seconds remaining to Superbowl or some other big event", you're much better off without socket.io. Just send the timestamp to the big event to the client and do the remainder on the client side.

Socket.io loses data on server side

Yellow,
so, I'm making a multiplayer online game on node (for funzies) and I'm stuck on a problem for over a week now. Perhaps the solution is simple, but I'm oblivious to it.
Long story short:
Data gets sent from client to server, this emit happens every
16.66ms.
Server receives them correctly and we collect all the data (lots of
fireballs in this case). We save them in player.skills_to_execute
array.
Every 5 seconds, we copy the data to seperate array (player_information), because
we are gona clean the current one, so it can keep collecting new
data, and then we send all the collected data back to the client.
Problem is definitely on server side. Sometimes this works, and sometimes it doesn't.
player_information is the array that I'm sending back to front, but before I send it, I do check with console.log on server if it does actually contain the data, and it does! But somehow that data gets deleted/overwritten right before sending it and it sends empty array (cause I check on frontend and I receive empty).
Code is fairly more complex, but I've minimized it here so it's easier to understand it.
This code stays on client side, and works as it should:
// front.js
socket.on("update-player-information", function(player_data_from_server){
console.log( player_data_from_server.skills_to_execute );
});
socket.emit("update-player-information", {
skills_to_execute: "fireball"
});
This code stays on server side, and works as it should:
// server.js
socket.on("update-player-information", function(data){
// only update if there are actually skills received
// we dont want every request here to overwrite actual array with empty [] // data.skills_to_execute = this will usually be 1 to few skills that are in need to be executed on a single client cycle
// naturally, we receive multiple requests in these 5 seconds,
// so we save them all in player object, where it has an array for this
if ( data.skills_to_execute.length > 0 ) {
player.skills_to_execute.push( data.skills_to_execute );
}
});
Now this is the code, where shit hits the fan.
// server.js
// Update player information
setInterval(function(){
// for every cycle, reset the bulk data that we are gona send, just to be safe
var player_information = [];
// collect the data from player
player_information.push(
{
skills_to_execute: player.skills_to_execute
}
);
// we reset the collected actions here, cause they are now gona be sent to front.js
// and we want to keep collecting new skills_to_execute that come in
player.skills_to_execute = [];
socket.emit("update-player-information", player_information);
}, 5000);
Perhaps anybody has any ideas?
Copy the array by value instead of by reference.
Try this:
player_information.push(
{
skills_to_execute: player.skills_to_execute.slice()
}
);
Read more about copying arrays in JavaScript by value or by reference here: Copying array by value in JavaScript

Node.js Fibers and code scheduled with setTimeout leads to crash

I am using Fibers to solve a problem regarding how to yield control to the event loop in node.js, pausing the execution of some synchronous code. This works well, mostly, but I encountered a strange crashing but, and I am not able to find the reason for it.
Setup
There are three process:
A main server process, it receives code to instrument and execute. When it receives new code to execute it use child_process.fork() to spawn
An execution process. This instruments the received code to call a specific callback from time to time to report what happened in the executed code. It then executes the code in a sandbox created by using Contextify. Sometimes these reports include incorrect location information about the line and column in the code something happens. In that case a source map is needed to map locations in the instrumented code to locations in the original code. But calculating this source map takes a significant amount of time. Therefore, before starting the execution the execution process spawns
A source map calculation process. This just takes the original code and the instrumented code and calculates a source map. When it's done it sends the finished source map to the execution process and exits.
If the execution process needs the source map in a callback before the execution is finished, it will use Fiber.yield() to yield control to the event loop and thus pause the execution. When the execution process then receives the data it continues the execution using pausedFiber.run().
This is implemented like so:
// server.js / main process
function executeCode(codeToExecute) {
var runtime = fork("./runtime");
runtime.on("uncaught exception", function (exception) {
console.log("An uncaught exception occured in process with id " + id + ": ", exception);
console.log(exception.stack);
});
runtime.on("exit", function (code, signal) {
console.log("Child process exited with code: " + code + " after receiving signal: " + signal);
});
runtime.send({ type: "code", code: code});
}
and
// runtime.js / execution process
var pausedExecution, sourceMap, messagesToSend = [];
function getSourceMap() {
if (sourceMap === undefined) {
console.log("Waiting for source map.");
pausedExecution = Fiber.current;
Fiber.yield();
pausedExecution = undefined;
console.log("Wait is over.")
}
if (sourceMap === null) {
throw new Error("Source map could not be generated.");
} else {
// we should have a proper source map now
return sourceMap;
}
}
function callback(message) {
console.log("Message:", message.type;)
if (message.type === "console log") {
// the location of the console log message will be the location in the instrumented code
/// we have to adjust it to get the position in the original code
message.loc = getSourceMap().originalPositionFor(message.loc);
}
messagesToSend.push(message); // gather messages in a buffer
// do not forward messages every time, instead gather a bunch and send them all at once
if (messagesToSend.length > 100) {
console.log("Sending messages.");
process.send({type: "message batch", messages: messagesToSend});
messagesToSend.splice(0); // empty the array
}
}
// function to send messages when we get a chance to prevent the client from waiting too long
function sendMessagesWithEventLoopTurnaround() {
if (messagesToSend.length > 0) {
process.send({type: "message batch", messages: messagesToSend});
messagesToSend.splice(0); // empty the array
}
setTimeout(sendMessagesWithEventLoopTurnAround, 10);
}
function executeCode(code) {
// setup child process to calculate the source map
importantDataCalculator = fork("./runtime");
importantDataCalculator.on("message", function (msg) {
if (msg.type === "result") {
importantData = msg.data;
console.log("Finished source map generation!")
} else if (msg.type === "error") {
importantData = null;
} else {
throw new Error("Unknown message from dataGenerator!");
}
if (pausedExecution) {
// execution is waiting for the data
pausedExecution.run();
}
});
// setup automatic messages sending in the event loop
sendMessagesWithEventLoopTurnaround();
// instrument the code to call a function called "callback", which will be defined in the sandbox
instrumentCode(code);
// prepare the sandbox
var sandbox = Contextify(new utils.Sandbox(callback)); // the callback to be called from the instrumented code is defined in the sandbox
// wrap the execution of the code in a Fiber, so it can be paused
Fiber(function () {
sandbox.run(code);
// send messages because the execution finished
console.log("Sending messages.");
process.send({type: "message batch", messages: messagesToSend});
messagesToSend.splice(0); // empty the array
}).run();
}
process.on("message", function (msg) {
if (msg.type === "code") {
executeCode(msg.code, msg.options);
}
});
So to summarize:
When new code is received a new process is created to execute it. This process first instruments and then executes it. Before doing so it starts a third process to calculate a source map for the code. The instrumented code calls the function named callback in the code above handing messages to the runtime that report progress of the executing code. These have to be adjusted sometimes, one example for which an adjustment is necessary are "console log" messages. To do this adjustment, the source map calculated by the third process is necessary. When the callback needs the source map it calls getSourceMap() which waits for the sourceMap process to finish its calculation and yields control to the event loop during that waiting time to enable itself to receive messages from the sourceMap process (otherwise the event loop would be blocked and no message could be received).
Messages passed to the callback are first stored in an array and then sent as a batch to the main process for performance reasons. However, we do not want the main process to wait too long for messages so in addition to sending a batch of messages when the threshold is reached we scheduled a function sendMessagesWithEventLoopTurnAround() to run in the event loop and check whether there are messages to send. This has two advantages:
When the execution process is waiting for the source map process it can use the time to send the messages it already got. So if the sourceMap process takes several seconds to finish, the main process does not have to wait the same time for messages that have already been created and contain correct data.
When the executing code generates only very little messages in the event loop (e.g. by a function scheduled with setTimeInterval(f, 2000) which only creates one single message per execution) it does not have to wait a long time until the message buffer is full (in this example 200s) but receives updates about the progress every 10ms (if anything changed).
The Problem
What works
This setup works fine in the following cases
I do not use fibers and a separate process to calculate the source map. Instead I calculate the source map before the code is executed. In that case all the code to execute I tried works as expected.
I do use fibers and a separate process and execute code for which I do not need the source map. E.g.
var a = 2;
or
setTimeout(function () { var a = 2;}, 10)
In the first case the output looks like this.
Starting source map generation.
Message: 'variables init'
Message: 'program finished'
Sending messages.
Finished source map generation.
Source map generator process exited with code: 0 after receiving signal: null
I do use fibers and a separate process and code for which I need the source map but that doesn't use the event loop, e.g.
console.log("foo");
In that case the output looks like this:
Starting source map generation.
Message: 'console log'
Waiting for source map generation.
Finished source map generation.
Wait is over.
Message: 'program finished'
Sending messages.
Source map generator process exited with code: 0 after receiving signal: null
I do use fibers and a separate process and code for which I need the source map and which uses the event loop, but the source map is only needed when the source map calculation is already finished (so no waiting).
E.g.
setTimeout(function () {
console.log("foo!");
}, 100); // the source map generation takes around 100ms
In that case the output looks like this:
Starting source map generation.
Message: 'function declaration'
Message: 'program finished'
Sending messages.
Finished source map generation.
Source map generator process exited with code: 0 after receiving signal: null
Message: 'function enter'
Message: 'console log'
Message: 'function exit'
Sending messages in event loop.
What doesn't work
It only breaks if I use fibers and separate processes and code that uses the event loop but needs the source map before it is finished, e.g.
setTimeout(function () {
console.log("foo!");
}, 10); // the source map generation takes around 100ms
The output then looks like this:
Starting source map generation.
Message: 'function declaration'
Message: 'program finished'
Sending messages.
Message: 'function enter'
Message: 'console log'
Waiting for source map generation.
/path/to/code/runtime.js:113
Fiber.yield();
^
getSourceMap (/path/to/code/runtime.js:113:28),callback (/path/to/code/runtime.js:183:9),/path/to/code/utils.js:102:9,Object.console.log (/path/to/code/utils.js:190:13),null._onTimeout (<anonymous>:56:21),Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
Child process exited with code: 8 after receiving signal: null
The process that crashes here is the execution process. However, I can't find out why that happens or how to track down the problem. As you can see above, I already added several log statements to find out what is happening. I am also listening to the "uncaught exception" event on the execution process, but that does not seem to be fired.
Also, the log message we see in the end is not one of mine, since I prefix my log messages with some kind of description string, so it's one created by node.js itself. I neither understand why this occurs, nor what exit code 8 or even what else I could do to narrow down the cause.
Any help would be greatly appreciated.
As usual, once one finishes describing the problem completely a solution presents itself.
The problem, I think, is that code executed by setTimeout is not wrapped in a Fiber. So calling Fiber.yield() inside that code crashes, understandably.
Therefore, the solution is to overwrite setTimeout in the executed code. Since I am already providing a sandbox with some special functions (e.g. my own console object) I can also exchange the implementation of setTimeout by one that wraps the executed function in a fiber, like so:
// this being the sandbox object, which si the global object for the executing code
this.setTimeout = function (functionToExecute, delay) {
return setTimeout(function () {
fibers(functionToExecute).run();
}, delay);
};
This implementation does not support passing additional parameters to setTimeout but it could trivially be expanded to do so. It also does not support the version of setTimeout that is passed a string of code instead of a function, but who would use that anyway?
To make it work completely I would have to exchange the implementations of setTimeout, setInterval, setImmediate and process.nextTick. Anything else that is usually used to fulfill such a role?
This only leaves the question whether there is an easier way to do this than reimplementing each of these functions?

Resources