Unable to persist a sockets array upon user connection - javascript

I have these two files:
io.js:
var io = require('socket.io')();
var socketioJwt = require('socketio-jwt');
var jwtSecret = require('./settings').jwtSecret;
io.set('authorization', socketioJwt.authorize({
secret: jwtSecret,
handshake: true
}));
io.on('connection', function(socket) {
IO.pushSocket(socket);
});
var IO = module.exports = {
io: io,
sockets: [],
pushSocket: function(socket) {
if (typeof IO.sockets === 'undefined') {
IO.sockets = [];
}
IO.sockets.push(socket);
console.log(IO.sockets);
}
}
main.js:
var sockets = require('./io').sockets;
console.log(sockets); \\ output is []
As you may notice, upon user connection I am trying to push to the sockets array in the IO module. But when I log the array in main.js it always returns as empty array. Any idea ?
Thank you.

You're fetching require('./io').sockets before the code (in pushSocket()) actually creates the array.
You cannot read an array before it exists.
You probably want to create the array immediately, so it will exist before you try to read it.

I'd suggest a bit of a different solution. You don't need to keep track of your own array of connected sockets at all because socket.io already does that for you. It provides both an array of sockets and a map of sockets (indexed by socket id):
// io.js
var io = require('socket.io')();
var socketioJwt = require('socketio-jwt');
var jwtSecret = require('./settings').jwtSecret;
io.set('authorization', socketioJwt.authorize({
secret: jwtSecret,
handshake: true
}));
io.on('connection', function(socket) {
// whatever you want to do here
});
module.exports = io;
Then, to use that module, you can do this:
// main.js:
var io = require('./io');
// then sometime later AFTER some sockets have connected
console.log(io.sockets.sockets); // array of connected sockets
Here are some of the data structures you can use from the io object:
io.sockets.sockets // array of connected sockets
io.sockets.connected // map of connected sockets, with socket.id as key
io.nsps // map of namespaces in use
io.nsps['/'].sockets // array of connected sockets in the "/" namespace (which is the default)
io.nsps['/'].connected // map of connected sockets in the "/" namespace
If you want to then track connect and disconnect events from outside the io module, you can just directly subscribe to the connection and disconnect events without having to invent your own scheme for it:
// main.js:
var io = require('./io');
io.on('connection', function(socket) {
// new socket just connected
console.log(io.sockets.sockets); // array of connected sockets
socket.on('disconnect', function() {
// socket just disconnected
});
});

Related

Express-generator, Socket.io Event issuing multiple times

I have create a node app using express generator. I have integrated socket.io in the application. Since express generator has their own way of creating express server i have followed this procedure to successfully integrate the Socket connection with listening server and made the io available throughout the application via res.io instance.
FILE: bin/www
#!/usr/bin/env node
var app = require('../app').app;
var debug = require('debug')('www:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = require('../app').server;
/app.js
//Express handler
var app = express();
// Socket configuration
var server = require('http').Server(app);
var io = require('socket.io')(server);
app.use(function(req, res, next){
res.io = io;
next();
});
...
module.exports = {app: app, server: server};
But the problem is when i m emitting an event as shown below. My client is reading the data multiple times.
routes/index.js
var clients = 0;
var nsp = res.io.of('/default-namespace');
nsp.on('connection', function (socket) {
clients++;
console.log(clients + ' clients connected!');
socket.on('disconnect', (reason) => {
clients--;
console.log(clients + ' clients connected!');
});
nsp.emit("socketToMe", "New User connected. Current clients:"+ clients);
});
My listener has the following code:
home.pug
var socket = io('/default-namespace');
socket.on('socketToMe', function (data) {
$('#data-div').append($('<li>').text(data));
});
Whenever i refresh the browser in another instance like incoginito my main browser is showing multiple events for the data. Like this
New User connected. Current clients:1
New User connected. Current clients:2
New User connected. Current clients:1
New User connected. Current clients:2
New User connected. Current clients:1
New User connected. Current clients:1
Not sure what is wrong. Can anyone help me on this?
Nodejs is event driven.The res object is not a global variable.
Express middleware runs for every request.
var clients = 0;
var nsp = res.io.of('/default-namespace');
nsp.on('connection', function (socket) {
clients++;
console.log(clients + ' clients connected!');
socket.on('disconnect', (reason) => {
clients--;
console.log(clients + ' clients connected!');
});
nsp.emit("socketToMe", "New User connected. Current
clients:"+clients);
});
Let me explain what happens above.A user requests and req handler is fired and you access the res object and you listen for events.
So for each request, you are listening for socket 'connection' event.That means you are setting multiple event listeners with the same name.Every time you make a request you set a new listener.
You are supposed to set only a single 'connection' listener.
This explains emitting the same event multiple times.
app.use(function(req, res, next){
res.io = io;
next();
});
Instead of using the above middleware function,listen directly on io instance

Express - socket.io - session. Refer to user id as socket id

I am using express-socket.io-session. I think I was able to setup the basic config by seeing the tutorials:
//BASIC CONFIG?
var clients = [];
var session = require("express-session")({
secret: 'some key',
resave: true,
saveUninitialized: true
});
var sharedsession = require("express-socket.io-session");
app.use(session);
io.use(function(socket, next){
next();
});
io.use(sharedsession(session, {
autoSave:true
}));
io.on('connection', function(socket) {
console.log("CLIENT CONNECTED");
var session = socket.handshake.session;
clients.push(socket);
socket.on('disconnect', function() {
console.log("CLIENT DISCONNECTED");
});
});
What I want to be able to do now is to refer to a specific client socket not by the socket but by the session id that should be assigned to that socket. When a user logins this happens:
req.session.user_id = user_id;
//(user_id is equal to DB {0,1,2,3...} ids
I was able to send sockets to specific clients when I did this:
clients[0].emit("to_do",info); // I don't know who is client index 0 compared to the login reference...
I would like to be able to do this or similar:
user_id = 3; // which would have a socket assigned
clients(user_id).emit("to_do",info);
That would mean each client would have a socket assigned to its previously assigned id. How could I do this so I could specify the socket by that id? I am not experienced at all with all of this so sorry for any big mistakes. Thanks
Your problem can be solved by each socket joining a group named after it's id:
socket.join(socket.id);
io.sockets.in(socket.id).emit('to_do', info);
//or
io.sockets.in(clients[0].id).emit('to_do', info);
Well I solved this out by iterating through the clients list and seeing which one had the socket I wanted
I ran into a similar issue, when using express-socket.io-session the user ID in socket.handshake.session.passport changes when a new user login, I used the below to solve it.
var userID;
if (!userID){
userID = socket.handshake.session.userID = socket.handshake.session.passport['user'];
}

Send messages from server to client socket.io

I am trying to send a message from NodeJS server to client using socket.io
However, I found the same practice all over the internet, which is wrapping the emit with io.on('connection', handler) and then making the server listen on a special "channel" event like so:
var io = require('socket.io')();
var socketioJwt = require('socketio-jwt');
var jwtSecret = require('./settings').jwtSecret;
var User = require('./models/users').User;
io.set('authorization', socketioJwt.authorize({
secret: jwtSecret,
handshake: true
}));
var sockets = [];
io.on('connection', function(socket) {
sockets.push(socket);
});
sendLiveUpdates = function(gameSession) {
console.log(sockets);
}
exports.sendLiveUpdates = sendLiveUpdates;
exports.io = io;
My problem is: I want to emit messages outside this on connection wrapper, example from my routes or other scripts. Is it possible?
Thanks.
Yes. You just need to keep a reference to the socket.
// Just an array for sockets... use whatever method you want to reference them
var sockets = [];
io.on('connection', function(socket) {
socket.on('event', function() {
io.emit('another_event', message);
});
// Add the new socket to the array, for messing with later
sockets.push(socket);
});
Then somewhere else in your code...
sockets[0].emit('someEvent');
What I usually do is assign new clients a UUID and add them to an object keyed by this UUID. This comes in handy for logging and what not as well, so I keep a consistent ID everywhere.

Connection Pool for NodeJS

I have an app that has been maxing out the number of connection to MongoDB and I was under the assumption that if the drivers were set up correctly you didn't need to worry about closing connections.
I've seen people mention the Generic Pool module but what is the best process for closing or pooling connections using Node & MongoDB?
Here is my connection code for the app:
var sys = require("sys");
var app = require('http').createServer(handler);
var io = require('socket.io').listen(app);
app.listen(1337);
io.configure(function () {
io.set('authorization', function (handshakeData, callback) {
callback(null, true);
});
});
function handler (req, res, data) {
sys.puts('request made to trackerapp.js');
res.writeHead(200);
res.end(data);
}
io.sockets.on('connection', function (socket) {
socket.on('adTracker', function (data) {
var adRequestData = data;
var databaseUrl = "mongodb://dbuser:dbpass#mongolab.com/tracker";
var collections = ["cmnads"]
var db = require("mongojs").connect(databaseUrl, collections);
db.cmnads.insert({adRequest : adRequestData},function(err, updated) {
if( err || !updated ) console.log("mongo not updated" + err);
else console.log("data stored");
});
});
});
After seeing JohnnyHK's comment I was able to pull the connection event out of the Socket.io connection and it worked fine, see the solution below:
var databaseUrl = "mongodb://dbuser:dbpass#mongolab.com/tracker";
var collections = ["cmnads"];
var db = mongojs.connect(databaseUrl, collections);
io.sockets.on('connection', function (socket) {
socket.on('adTracker', function (data) {
var adRequestData = data;
//vars for MongoDB used to be created here... so new connect function was called on every request to socket.io
db.cmnads.insert({adRequest : adRequestData},function(err, updated) {
if( err || !updated ) console.log("mongo not updated" + err);
else console.log("data stored");
});
});
});
A technique I used with my express apps that seems have some measure of success is to open a connection to a mongo instance (thereby getting a connection pool) then sharing that db (that is now in the "connected" state) instance wherever it is needed. Something like this:
server = new Server(app.settings.dbsettings.host, app.settings.dbsettings.port, {auto_reconnect: true, poolSize: 5})
db = new Db(app.settings.dbsettings.db, server, {native_parser:false})
db.open(function(err, db) {
app.db = db;
server = app.listen(app.settings.port);
console.log("Express server listening on port %d in %s mode", app.settings.port, app.settings.env);
require('./apps/socket-io')(app, server);
});
This connects to the database at the highest level in my app before the program moves into the wait listen state.
Before I used this pattern I would create a new database object whenever I needed to interact with the database. The problem I found is that the new database object would create a new thread pool, consuming a bunch of ports. These were never cleaned up properly. After a period of time the machine that hosted the app would run out of ports!
Anyway, a variation on the code I have shown should be where you should do your thinking I believe.

Creating a map of ids to sockets and vice versa in Node.js

I'm trying to manage a bunch of socket connections. My app is basically an http server that receives posts and passes these along to a socket. When clients open a socket connection, they send a connect message with an id:
{"m":"connect","id":"1"}
The app then saves this id and socket in the id2socket and socket2id maps. On disconnect, the socket/id pair is deleted from the maps.
A post will also contain an id, which indicates the post data should be sent to the socket with that id.
That's great, and this works fine for a single open socket. However, when I have more than one socket open, and then I close a socket, that disconnect wipes everything from the map. I think my understanding of sockets in node is incomplete- is there only a single socket object that is used in the callback? Is there a better way to manage my open socket connections and ids?
start server:
>>node server.js
TCP server listening on 127.0.0.1:5280
HTTP server listening on 127.0.0.1:9002
telnet in:
>>telnet localhost 5280
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
{"m":"connect","id":"123"}
{"m":"connect","id":"123","success":"true"}
server after connection:
>>Connection from 127.0.0.1:57572
received data: {"m":"connect","id":"123"}
id: 1
m: connect
associating uid 1 with socket [object Object]
do a post:
python post.py {"foo":"bar"}
So this works fine for several open sockets (as long as 1 device is id 123, server has this hardwired for now). However, as soon as you close one connection all the socket connections are removed from the map.
Here's my code:
python script to do post:
import sys
import json
import httplib, urllib, urllib2
values = json.loads('{"foo":"bar"}')
headers = {"Content-type": "application/json"}
conn = httplib.HTTPConnection('127.0.0.1', 9002)
headers = {"Content-type": "application/json"}
conn.request("POST", "", json.dumps(values), headers)
response = conn.getresponse()
print "response.status: "+response.status
print "response.reason: "+response.reason
print "response.read: "+response.read()
conn.close()
node server (http and tcp), hardwired to send data to device '123' on post:
var net = require('net'); // tcp-server
var http = require("http"); // http-server
var qs = require('querystring'); // http-post
// Map of sockets to devices
var id2socket = new Object;
var socket2id = new Object;
// Setup a tcp server
var server_plug = net.createServer(function(socket) {
// Event handlers
socket.addListener("connect", function(conn) {
console.log("Connection from " + socket.remoteAddress + ":" + socket.remotePort );
});
socket.addListener("data", function(data) {
console.log("received data: " + data);
try {
request = JSON.parse(data);
response = request;
if(request.m !== undefined && request['id'] !== undefined){ // hack on 'id', id is js obj property
console.log("id: "+request['id']);
console.log("m: "+request.m);
if(request.m == 'connect'){
console.log("associating uid " + request['id'] + " with socket " + socket);
id2socket[request['id']] = socket;
socket2id[socket] = request['id'];
response.success = 'true';
} else {
response.success = 'true';
}
}
socket.write(JSON.stringify(response));
} catch (SyntaxError) {
console.log('Invalid JSON:' + data);
socket.write('{"success":"false","response":"invalid JSON"}');
}
});
socket.on('end', function() {
id = socket2id[socket]
console.log("socket disconnect by id " + id);
// wipe out the stored info
console.log("removing from map socket:"+socket+" id:"+id);
delete id2socket[id];
delete socket2id[socket];
});
socket.on('timeout', function() {
console.log('socket timeout');
});
});
// Setup http server
var server_http = http.createServer(
// Function to handle http:post requests, need two parts to it
// http://jnjnjn.com/113/node-js-for-noobs-grabbing-post-content/
function onRequest(request, response) {
request.setEncoding("utf8");
request.addListener("data", function(chunk) {
request.content += chunk;
});
request.addListener("end", function() {
console.log("post received!");
//console.log("Request received: "+request.content);
if (request.method == 'POST') {
//var json = qs.parse(request.content);
//console.log("Post: "+json);
// HACK TO TEST STUFF:
// send a message to one of the open sockets
try {
var socket = id2socket['123']; //hardwired
socket.write('{"m":"post"}');
} catch (Error) {
console.log("Cannot find socket with id "+'123');
}
}
});
}
);
// Fire up the servers
var HOST = '127.0.0.1';
var PORT = 5280;
var PORT2 = 9002;
server_plug.listen(PORT, HOST);
console.log("TCP server listening on "+HOST+":"+PORT);
server_http.listen(PORT2);
console.log("HTTP server listening on "+HOST+":"+PORT2);
Objects only take strings as keys for their properties. As your log shows, a socket object is converted into the string "[object Object]". As a result, socket #2 overwrites the id from socket #1 in the object, because all sockets are converted into the same string key. So, there is only one property in the object at all times, because all sockets come down to the same key. When you try to remove the id for socket #2, the single property is deleted and the object is empty.
You seem to want a custom property for each separate socket when used as a key. You can use WeakMaps for this. WeakMaps do allow objects as keys (as opposed to string-only keys), but as they're relatively new they may contain bugs at the moment.
(Note that the id2socket map can just be a plain object, because numbers are converted into strings just fine, and each number has its own, distinct string representation*.)
Using WeakMaps is as follows:
var socket2id = new WeakMap; // as if you were doing: var socket2id = {};
socket2id.set(socket, id); // as if you were doing: socket2id[socket] = id;
socket2id.get(socket); // as if you were doing: socket2id[socket];
socket2id.delete(socket); // as if you were doing: delete socket2id[socket];
Make sure to run with node --harmony (>= 0.7) or node --harmony_weakmaps (<= 0.6).
* 0 and -0 are exceptions, but you shouldn't be using -0 anyway because 0 === -0, so it's difficult to differ between them.

Categories

Resources