How to add additional handlers to an existing connection? - javascript

Within my MVC 5 application, I am setting up a Signal R connection on the client end upon page load, this works as expected.
At some point later on I want add an additional handler and make a server side call, I can see that the server recieves this call which then initiates some client side calls, the handlers at the client don't get invoked.
Connection setup upon page load
function initialiseRealTimeDataRetrieval() {
var hub = $.connection.autoGeneratedProxyForHub;
hub.client.recieveRealTimeData = function (data) {
//Do Stuff
};
$.connection.hub.start().done(function () {
hub.server.getRealTimeData();
});
}
Additional calls made later on
function initialiseFeed () {
var hub = $.connection.autoGeneratedProxyForHub;
hub.client.recieveRealTimeDataFeed = function (data) {
//Do stuff
};
if ($.connection.hub.state == $.connection.connectionState.connected) {
hub.server.getRealTimeDataFeed();
}
else {
$.connection.hub.start().done(function () {
hub.server.getRealTimeDataFeed();
});
}
}
So far I have tried the following:
Made sure that calls made from the client to server are being invoked on the server.
Made sure that the additional calls are work as expected if they were made along with the calls and handlers executing upon page load.
Reviewd documentation to see if a connection must be restarted to register the new handlers.
Attempted various methods of restarting the connection after new handlers were added
The below works as expected for the additional calls however makes everything done for the connection upon page load redundant:
function initialiseFeed () {
var hub = $.connection.autoGeneratedProxyForHub;
hub.client.recieveRealTimeDataFeed = function (data) {
//Do stuff
};
$.connection.hub.stop();
$.connection.hub.start().done(function () {
hub.server.getRealTimeDataFeed();
});
}
Inspecting the hub object through the debugger does show that all clients are connected, including the additional ones.

According to the Signal R JS API Docs, the automaically generated proxy for the hub can't be used to register multiple event handler:
When to use the generated proxy
If you want to register multiple event handlers for a client method
that the server calls, you can't use the generated proxy. Otherwise,
you can choose to use the generated proxy or not based on your coding
preference. If you choose not to use it, you don't have to reference
the "signalr/hubs" URL in a script element in your client code.
Also to register new handlers for an existing connection, that connection must have at least one handler associated with it prior to establishing a connection, upon registering new handlers you must call start():
Note
Normally you register event handlers before calling the start method
to establish the connection. If you want to register some event
handlers after establishing the connection, you can do that, but you
must register at least one of your event handler(s) before calling the
start method. One reason for this is that there can be many Hubs in an
application, but you wouldn't want to trigger the OnConnected event on
every Hub if you are only going to use to one of them. When the
connection is established, the presence of a client method on a Hub's
proxy is what tells SignalR to trigger the OnConnected event. If you
don't register any event handlers before calling the start method, you
will be able to invoke methods on the Hub, but the Hub's OnConnected
method won't be called and no client methods will be invoked from the
server.

Related

Socket is called multiple times

I read other questions about this issue but didn't find a solution for the problem. I saw that you can removeListeners or move the socket code out of the click handler for example but it doesn't seem to fit my problem.
I am using sockets + ajax. My code logic goes as follows:
//client side
button.onclick = function () {Communicate(info);return false;};
function Communicate(info) {
// ajax call
}
// server side -> receive ajax
app.post("/...",function(req,res) {
// do stuff (...)
// send global socket call
io.sockets.emit("GlobalCall",xx);
// return ajax call to the user who triggered this
res.send(...));
});
// client side again
socket.on("GlobalCall",function(x)) {
// this is called twice sometimes for some reason... It should be called only once each time the server emits ...
}
As I commented on the code, the server emits only once but "GlobalCall" happens twice sometimes.
Any help? Thank you
The solution wasn't really related to sockets. What was happening had to do with jquery animations that were executed twice for each assigned property. I didn't know that and didn't notice it. Wrong to blame the sockets.

Calling socket.disconnect in a forEach loop doesn't actually call disconnect on all sockets

I am new to javascript world. Recently I was working on a chat application in nodejs. So I have a method called gracefulshutdown as follows.
var gracefulShutdown = function() {
logger.info("Received kill signal, shutting down gracefully.");
server.close();
logger.info('Disconnecting all the socket.io clients');
if (Object.keys(io.sockets.sockets).length == 0) process.exit();
var _map = io.sockets.sockets,
_socket;
for (var _k in _map) {
if (_map.hasOwnProperty(_k)) {
_socket = _map[_k];
_socket.disconnect(true);
}
}
...code here...
setTimeout(function() {
logger.error("Could not close connections in time, shutting down");
process.exit();
}, 10 * 1000);
}
Here is what is happening in the disconnect listener.The removeDisconnectedClient method simply updates an entry in the db to indicate the removed client.
socket.on('disconnect', function() {
removeDisconnectedClient(socket);
});
So in this case the disconnect event wasn't fired for all sockets. It was fired for only a few sockets randomly from the array. Although I was able to fix it using setTimeout(fn, 0) with the help of a teammate.
I read about it online and understood only this much that setTimeout defers the execution of of code by adding it to end of event queue. I read about javascript context, call stack, event loop. But I couldn't put together all of it in this context. I really don't understand why and how this issue occurred. Could someone explain it in detail. And what is the best way to solve or avoid them.
It is hard to say for sure without a little more context about the rest of the code in gracefulShutdown but I'm surprised it is disconnecting any of the sockets at all:
_socket = _map[ _k ];
socket.disconnect(true);
It appears that you are assigning an item from _map to the variable _socket but then calling disconnect on socket, which is a different variable. I'm guessing it is a typo and you meant to call disconnect on _socket?
Some of the sockets might be disconnecting for other reasons and the appearance that your loop is disconnecting some but not all the sockets is probably just coincidence.
As far as I can tell from the code you posted, socket should be undefined and you should be getting errors about trying to call the disconnect method on undefined.
From the method name where you use it I can suppose that application exits after attempts to disconnect all sockets. The nature of socket communication is asynchronous, so given you have a decent amount of items in _map it can occur that not all messages with disconnect will be sent before the process exits.
You can increase chances by calling exit after some timeout after disconnecting all sockets. However, why would you manually disconnect? On connection interruption remote sockets will automatically get disconnected...
UPDATE
Socket.io for Node.js doesn't have a callback to know for sure that packet with disconnect command was sent. At least in v0.9. I've debugged that and came to conclusion that without modification of sources it is not possible to catch that moment.
In file "socket.io\lib\transports\websocket\hybi-16.js" a method write is called to send the disconnect packet
WebSocket.prototype.write = function (data) {
...
this.socket.write(buf, 'binary');
...
}
Whereas socket.write is defined in Node.js core transport "nodejs-{your-node-version}-src\core-modules-sources\lib\net.js" as
Socket.prototype.write = function(chunk, encoding, cb)
//cb is a callback to be called on writeRequest complete
However as you see this callback is not provided, so socket.io will not know about the packet having been sent.
At the same time when disconnect() is called for websocket, member disconnected is set to true, and "disconnect" event is broadcasted, indeed. But synchronously. So .on('disconnect' handler on server socket doesn't give and valuable information about whether the packet was sent or not.
Solution
I can make a general conclusion from this. If it is so critical to make sure that all clients are immediately informed (and not wait for a heartbeat timeout or if heartbeat is disabled) then this logic should be implemented manually.
You can send an ordinary message which will mean for the client that server is shutting down and call socket disconnect as soon as the message is received. At the same time server will be able to accept all acknowledgements
Server-side:
var sockets = [];
for (var _k in _map) {
if (_map.hasOwnProperty(_k)) {
sockets.push(_map[_k]);
}
}
sockets.map(function (socket) {
socket.emit('shutdown', function () {
socket.isShutdown = true;
var all = sockets.every(function (skt) {
return skt.isShutdown;
});
if (all) {
//wrap in timeout to let current tick finish before quitting
setTimeout(function () {
process.exit();
});
}
})
})
Clients should behave simply
socket.on('shutdown', function () {
socket.disconnect();
});
Thus we make sure each client has explicitly disconnected. We don't care about server. It will be shutdown shortly.
In the example code it looks like io.sockets.sockets is an Object, however, at least in the library version I am using, it is a mutable array which the socket.io library is free to modify each time you are removing a socket with disconnect(true).
Thus, when you call disconnect(true); if the currently iterated item from index i is removed, this effect like this happens:
var a = [1,2,3,4];
for( var i in a) {
a.splice(i,1); // remove item from array
alert(i);
}
// alerts 0,1
Thus, the disconnect(true) call will ask the socket.io to remove the item from the array - and because you are both holding reference to the same array, the contents of the array are modified during the loop.
The solution is to create a copy of the _map with slice() before the loop:
var _map = io.sockets.sockets.slice(); // copy of the original
It would create a copy of the original array and thus should go through all the items in the array.
The reason why calling setTimeout() would also work is that it would defer the removal of the items from the array, allowing the whole loop iterate without modifying the sockets -Array.
The problem here is that sockjs and socket.io use asynchronous "disconnect" methods. IE. When you call disconnect, it is not immediately terminated. It is just a promise that it WILL be terminated. This has the following effect (assuming 3 sockets)
Your for loop grabs the first socket
The disconnect method is called on the first socket
Your for loop grabs the second socket
The disconnect method is called on the second socket
The disconnect method on the first socket finishes
Your for loop grabs the third socket
The disconnect method is called on the third socket
Program kills itself
Notice, that sockets 2 and 3 haven't necessarily finished yet. This could be for a number of reasons.
Finally, setTimeout(fn, 0) is, as you said, blocking the final call, but it may not be consistent (I haven't dug into this too much). By that I mean, you've set the final termination to be AFTER all your sockets have disconnected. The setTimeout and setInterval methods essentially act more like a queue. Your position in the queue is dictated by the timer you set. Two intervals set for 10s each, where they both run synchronously will cause one to run AFTER the other.
After Socket.io 1.0, the library does not expose you an array of the connected sockets. You can check that io.socket.sockets.length, is not equal to the open socket objects. Your best bet is that you broadcast a 'disconnect' message to all the clients that you want to off, and on.'disconnect' on the client side close the actual WebSocket.

SignalR-Hub after IIS stop,start will no longer call client functions

I have a queue system using SignalR 2.1.1 with Angular. Everything is working perfectly actually. However when I decided to test the system against an IIS outage I noticed a problem. When I stop, then start IIS, IIS restart doesn't cause the issue, my javascript functions that the hub calls will no longer fire. That makes sense to me, but the problem is that the client can still call the server without any issue so the user has no idea they are disconnected. This would certainly mess up my queue state.
So, the solution would seem to be able to test this disconnect somehow and reconnect if necessary. Is there a way to test to see if the client functions my hub is calling are still connected? It seems that since I can call the hub that it should have to reconnect although I don't see any of that activity happening. I've tried the disconnected, reconnecting, stateChanged events on the client side to see if I could catch that happening with no luck.
Thank you for any assistance
So my solution was to create a method on the hub that only responds to the caller:
public void LastChange()
{
Clients.Caller.lastChange();
}
I hooked that call back to this function in my Angular controller:
vm.queueHub.client.lastChange = function onLastChange()
{
vm.lastChangeCalledBack = true;
}
Also in my controller I created this function that tests for the lastChangeCalledBack variable which is set by the function the hub calls. If it's not set after some interval testing I assume we've lost connection:
vm.stillAlive = function()
{
vm.queueHub.server.lastChange();
var found = $interval(function()
{
if (vm.lastChangeCalledBack == true)
{
vm.lastChangeCalledBack = false;
$interval.cancel(found);
}
}, 100, 10);
return found;
}
Finally I created this function in my controller and call it from any functions that make queue changes from the UI and pass in the callback to call if the connection is still valid. For some reason the promise seems to be reverse of what the Angular documentation says, but I must be misunderstanding: $interval docs
function verifyConnection(callback)
{
vm.stillAlive().then(
function (data) {
console.log("Lost connection with server: " + data);
signalrFactory.start();
var reconnectedMessage = "There was a server disconnect. Your connection has been re-established, but you should reload your browser."
getQueue(function () { alert(reconnectedMessage); });
},
function (data) {
console.log("Server connection intact: " + data);
callback();
}
);
}
So for example, this is called from the UI to open a modal:
vm.open = function (item)
{
verifyConnection(function () {
openFlagModal(item);
});
};
I also plan to call the verifyConnection() function periodically as well. This solution seems to work and keep all the clients in sync with the server no matter what. However, I don't like the fact that the SignalR client is already sending pings to the server, and re-establishing the connection, just not reconnecting the callback client methods. It makes me wonder if I'm doing something wrong to cause the client functions to not get reconnected.
Any thoughts on this solution?

What the difference between `onmessage` and `.addEventListener`?

I'm trying to get data with server-sent event, what the different using
source.onmessage vs source.addEventListener?
source.onmessage is the built in function wrapper for EventSource that is triggered when new data is sent to the client. It fires when no event attribute is returned (default) and doesn't fire when it is set.
addEventListener is similar, but differs in that it listens for a specific event name, and triggers on its presence, allowing you to separate your functionality for multiple events. You can then parse the JSON data returned. It can be used on any event type. Have a look at this example:
source.addEventListener("login", function(e) {
// do your login specific logic
var returnedData = JSON.parse(e);
console.log(returnedData);
}, false);
This snippet will listen for a server message with event specified as login, then it triggers the callback function.
More info:
https://developer.mozilla.org/en-US/docs/Server-sent_events/Using_server-sent_events
http://html5doctor.com/server-sent-events/
I assume you're talking about addEventListener('message') vs onmessage. They do the same thing, but I'd recommend using onmessage because with addEventListener, there's always a possibility of unexpectedly adding the same listener twice, e.g. due to a laggy page reload, or some hot-reload during development. In those cases the handler function could fire twice on every event, which leads to weird behaviors.

What does addListener do in node.js?

I am trying to understand the purpose of addListener in node.js. Can someone explain please? A simple example would be:
var tcp = require('tcp');
var server = tcp.createServer(function (socket) {
socket.setEncoding("utf8");
socket.addListener("connect", function () {
socket.write("hello\r\n");
});
socket.addListener("data", function (data) {
socket.write(data);
});
socket.addListener("end", function () {
socket.write("goodbye\r\n");
socket.end();
});
});
server.listen(7000, "localhost");
Due to the fact that Node.js works event-driven and executes an event-loop, registering listeners allow you to define callbacks that will be executed every time the event is fired. Thus, it is also a form of async. code structuring.
It's comparable to GUI listener, that fire on user interaction. Like a mouse click, that triggers an execution of code in your GUI app, your listeners in your example will be run as soon as the event happens, i.e. a new client connects to the socket.
it registers a listener for an "event". Events are identified by strings, such as "connect" and "data". the second argument is a function, a so called "callback", also refered to as "event handler". Whenever a specific event occurs within the object the listeners have been registered to, all handlers are invoked.
node.js uses this, because it employs an asynchronous execution model, that can best be handled with an event-driven approach.
greetz
back2dos

Categories