This is more of a best practices question I assume, but in the following, can anyone tell me how I would pass a reference of the socket object outside of the method handler it is in?
io.on('connection', function (socket) {
console.log('connection '+socket);
//do stuff here
});//end callback handler
//need a reference to socket here!!
Just assign it to a variable defined in that scope?
var globalSocket;
io.on('connection', function (socket) {
globalSocket = socket;
});
// now what?
However, I don't see a reason to do this since you won't be able to use the value until the event handler was called (which you only know when the event handler was called).
To solve that, you would need to put the code that needs access to the socket in a function and call the function from the event handler:
var globalSocket;
io.on('connection', function (socket) {
globalSocket = socket;
doSomething();
});
function doSomething() {
// do something with globalSocket
console.log(globalSocket);
}
but then it would be much more elegant to pass socket as argument to the function:
io.on('connection', function (socket) {
doSomething(socket);
});
function doSomething(socket) {
// do something with socket
console.log(socket);
}
Well, you have to store this in some sort of socket connection pool. But keep in mind that if you want to distribute in several process/machines with loadbalancers, this will not work very well.
Related
I'm new to javascript and have been following this tutorial series on how sockets work: https://www.youtube.com/watch?v=HZWmrt3Jy10.
He writes:
var io = socket(server);
io.sockets.on('connection', newConnection);
function newConnection(socket){
console.log('new connection' + socket.id);
}
How does newConnection work in the sockets.on? It needs a socket parameter yet it works, it is a reference to the function as well. I have heard of callbacks vaguely but am still confused. Can someone please explain what is happening.
Functions in Javascript are "first class" objects and they can be passed around, stored in variables etc... consider this simple example:
function foo(x) {
console.log("foo(" + x + ") called");
}
function bar(f) {
f(42);
}
bar(foo);
last line calls the function bar passing it as a parameter the function foo. The function bar then calls the function f that it received passing 42 as argument.
The net result will be the message foo(42) called appearing in console.
In the code you show what happens is that the on function will store away the argument to call it later (providing the socket as parameter) once a connection is established.
You are passing your function so the socket.on method just like you would be passing a variable. And just like a variable, the code inside socket.on can then use your function including passing you the required parameter.
The socket.on() method would be implemented something like this:
Socket.prototype.on = function (event, callback) {
// other logic
callback(socket); // pass socket to your newConnection function
}
So when you call:
io.sockets.on('connection', newConnection);
you are passing your function newConnection as a callback (this can be named anything by the programmer who wrote socket.on) and then it will call your function as:
callback(socket); // callback is second parameter so
// in your case it is pointing to
// newConnection
so it passes the socket to your function.
I've got a code setup and I would like to know if it gets garbage-collected so my system won't start swapping and freeze/crash.
The setup should be able to run an undetermined amount of time assuming you stay within the normal parameters of your system (e.g. so don't connect 1.000.000 users to one single thread process all at the same time)
I've made a simplified version.
// connection.js
var io, Handler;
io = require('socket.io');
Handler = require('./handler');
io.on('connection', function (socket) {
new Handler({socket: socket})
});
// handler.js
var Handler,
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
function Handler(arg) {
this.socket = arg.socket;
this.test = bind(this.test, this);
console.log('Handler', 'New connection', this.socket.id);
this.listeners();
}
Handler.prototype.listeners = function() {
return this.socket.on('test', this.test);
};
Handler.prototype.test = function() {
return console.log('SocketIO', 'test', this.socket.id);
};
module.exports = Handler;
So is 'new Handler' garbage-collected after a 'socket disconnect event' has occurred?
If not how should I change the code?
At first I thought this might solve the problem
var io, connections, Handler;
io = require('socket.io');
Handler = require('./handler');
connections = {}
io.on('connection', function (socket) {
connections[socket.id] = new Handler({socket: socket});
socket.on('disconnect', function (){
connections[socket.id] = null
})
});
But this only creates a reference to the 'new Handler' and removes that same reference on disconnect. That doesn't necessarily mean the 'new Handler' will be removed as well. It could still be listening for and/or doing stuff.
Your Handler object should get garbage collected as long as no other code of yours is keeping a reference to it.
When the actual socket itself is closed, the system knows that the event handlers are no longer live and thus they don't prevent garbage collection. The browser does the same sort of thing when you remove a DOM object that has event handlers on it so that these event handlers don't prevent garbage collection.
So, when the socket is closed and socket.io releases any references it has to the Javascript socket object, there should be no code that has any references to the Handler object any more and it will be eligible for garbage collection.
If you make your own reference to the Handler object such as:
var connections = {};
io.on('connection', function (socket) {
connections[socket.id] = new Handler({socket: socket});
});
Then, you will have to clear that reference upon disconnect as in the code below. Otherwise, this reference will stay alive and will keep the Handler object from being garbage collected.
var connections = {};
io.on('connection', function (socket) {
connections[socket.id] = new Handler({socket: socket});
socket.on('disconnect', function() {
delete connections[socket.id];
})
});
Note: I'd recommend you actually use delete to remove the property rather than just setting it to null. That way you won't get unused properties building up on the connections object.
io.on('connection', function(socket) {
socket.join('myRoom');
if(condition){
bindFunctions();
}
});
Let's say condition is true when the 4th socket connects. The code below successfully binds a .on handler to all the sockets in myRoom.
bindFunctions(){
for(var socketId in io.sockets.adapter.rooms["myRoom"]){
var socket = io.sockets.connected[socketId];
socket.on('my event',function(submit){
console.log(socket.myVariable);
//This is always the same property, no matter who calls this function.
});
}
}
However, when 'my event' fires, regardless of which client triggered it, the socket object is always the 4th socket that connected. It is always the socket that called bindFunctions. I would have thought that the handler socket should be the same as the socket inside the function.
So I have two questions:
Why is the socket inside the 'my event' function the socket that called bindFunctions instead of the socket that fired the event?
What can I do instead to be able to access properties of the socket that called 'my event' whilst inside the function?
That happens because although you're doing var socket = io.sockets.connected[socketId]; inside the loop, in Javascript, the var keyword is function scoped, and not block scoped.
Under the guts, what really happens is:
bindFunctions(){
var socket;
for(var socketId in io.sockets.adapter.rooms["myRoom"]){
socket = io.sockets.connected[socketId];
socket.on('my event',function(submit){
console.log(socket.myVariable);
//This is always the same property, no matter who calls this function.
});
}
}
So, since your event will happen later, your socket variable is always the last one set.
To prevent this behavior, if using Node.js version > 4.0.0, you can use const instead of var, because it's block scoped. But in Node.js, to be able to use const and let keywords block-scoped, you must put 'use strict'; in the begining of your function, e.g:
bindFunctions(){
'use strict';
for(var socketId in io.sockets.adapter.rooms["myRoom"]){
const socket = io.sockets.connected[socketId];
socket.on('my event',function(submit){
console.log(socket.myVariable);
//This is always the same property, no matter who calls this function.
});
}
}
Another way of solving your problem is by creating an IIFE, so you always pass the current variable to the event, e.g:
bindFunctions(){
for(var socketId in io.sockets.adapter.rooms["myRoom"]){
var socket = io.sockets.connected[socketId];
(function(socket) {
socket.on('my event',function(submit){
console.log(socket.myVariable);
});
})(socket);
}
}
Doing that, the socket variable inside the IIFE will always refer to the object passed as parameter, and you'll get your desired behavior.
You can also use Object.keys combined with Array.prototype.forEach instead of for...in loop:
bindFunctions(){
var room = io.sockets.adapter.rooms["myRoom"];
Object.keys(room).forEach(function(key) {
var socketId = room[key];
var socket = io.sockets.connected[socketId];
socket.on('my event',function(submit){
console.log(socket.myVariable);
});
});
}
Doing that, you're creating a new context each iteration, since it's a callback function being called, and you won't have problems with function scoping.
I seem to lack understanding of how to structure node modules.
I have the following in app.js.
var io = require('socket.io')(http);
io.on('connection', function(socket){
socket.on('disconnect', function(){
console.log('user disconnected');
});
console.log("New user " + socket.id);
users.push(socket.id);
io.sockets.emit("user_count", users.length);
});
And this is fine. I can react to all kinds of messages from the client, but I also have several modules that need to react to different messages. For example my cardgame.js module should react to:
socket.on("joinTable"...
socket.on("playCard"
While my chessgame.js should react to
socket.on("MakeAMove"...
and my user.js file handles:
socket.on('register' ...
socket.on('login' ...
How would I link up/structure my files to handle these different messages, so that my file that reacts to socket requests does not become too huge.
Basically it would be great if I could pass the socket object to these modules. But the issue is that until a connection is established the socket will be undefined.
Also if I pass the whole io variable to my modules, then each of those modules would have the io.on('connection',..) call. Not sure if that is even possible or desired.
You don't need to pass the whole io object around (but you can, I do just in case I need it). Just pass the socket to the modules on connection, and then set your specific on callbacks for the module
main
io.on("connection",function(socket){
//...
require("../someModule")(socket);
require("../smoreModule")(socket);
});
socket
//Convenience methods to setup event callback(s) and
//prepend socket to the argument list of callback
function apply(fn,socket,context){
return function(){
Array.prototype.unshift.call(arguments,socket);
fn.apply(context,arguments);
};
}
//Pass context if you wish the callback to have the context
//of some object, i.e. use 'this' within the callback
module.exports.setEvents = function(socket,events,context){
for(var name in events) {
socket.on(name,apply(events[name],socket,context));
}
};
someModule
var events = {
someAction:function(socket,someData){
},
smoreAction:function(socket,smoreData){
}
}
module.exports = function(socket){
//other initialization code
//...
//setup the socket callbacks for the connected user
require("../socket").setEvents(socket,events);
};
I just wanted to confirm a suspicion of mine.
I stumbled across an article which recommended using Socket.io in the following fashion:
var app = require('express').createServer()
var io = require('socket.io').listen(app);
app.listen(8080);
// Some unrelated stuff
io.sockets.on('connection', function (socket) {
socket.on('action1', function (data) {
// logic for action1
});
socket.on('action2', function (data) {
// logic for action2
});
socket.on('disconnect', function(){
// logic for disconnect
});
});
I feel like the following would be a better use of resources:
var app = require('express').createServer()
var io = require('socket.io').listen(app);
app.listen(8080);
// Some unrelated stuff
io.sockets.on('connection', function (socket) {
socket.on('action1', action1);
socket.on('action2', action2);
socket.on('disconnect', disconnect);
});
function action1(data) {
// logic for action1
}
function action2(data) {
// logic for action2
}
function disconnect() {
// logic for disconnect
}
My feeling is that although the anonymous function that handles the connection event is only created in memory once, the anonymous functions that handle action1, action2, and disconnect are created in memory for every socket connection. The issue with the second approach is that socket is no longer in scope.
So firstly, is my suspicion about the creation of functions true? And secondly, if so is there a way to get socket in scope for the named functions?
Using a closure helps to keep the scope clean:
io.sockets.on('connection', function () {
function action1(data) {
// logic for action1
}
function action2(data) {
// logic for action2
}
function disconnect() {
// logic for disconnect
}
return function (socket) {
socket.on('action1', action1);
socket.on('action2', action2);
socket.on('disconnect', disconnect);
}
}()); // <- note the immediate function call
To your questions:
So firstly, is my suspicion about the creation of functions true?
Yes. The closure approach above prevents this, the callback functions are created only once. Plus: all see the correct parent scopes.
And secondly, if so is there a way to get socket in scope for the named functions?
The socket will be available as this in the callbacks.
You're correct, that the anonymous methods are created for each connection - and if you don't need scope, then the second method does avoid that. If you need the socket scope there's no real way to avoid it. If you want to keep the methods external (for some other reason) and still keep scope, you could always:
//...
socket.on('action1', function(){
action1.apply( socket, arguments );
} );
//... and so forth.
But that has you back to creating a method signature for each connection, so I'm not sure what you'd be gaining.