My socketio collecting users script;
var users = [];
io.sockets.on( 'connection', function( socket ) {
socket.on('new user', function(data) {
socket.nickname = data.nick;
users[socket.nickname] = socket;
});
});
I want to remove the disconnected user. Done this;
socket.on('disconnect', function() {
delete users[socket.nickname];
});
After that, there is no errors. But when I do
console.log(typeof users[socket.nickname]);
it gives me output of object
Also there are still some garbage data left about the user. (socket events listeners, namespace). What is the proper way to remove user completely without leaving any data ?
socket.on('disconnect', function() {
socket.get('nickname', function(err, user) {
delete users[user];
io.sockets.emit('update', users);
});
});
Related
I want to create a live order page where clients can see the status of their order.
For that reason I want to run a function every 10 seconds that checks the SQL database if the order is ready.
function checkOrder(socket, userid, checkinterval) {
pool.getConnection(function(err, connection) {
// Use the connection
connection.query('SELECT * FROM orders WHERE user = ' + userid + ' ORDER BY timestamp DESC', function(err, rows) {
var alldone = false;
for (var i = 0; i < rows.length; i++) {
if (rows[i]['status'] == 'completed') {
alldone = true;
} else {
alldone = false;
break;
}
}
socket.emit('order-update', rows);
connection.release();
if (alldone) {
console.log('all done');
socket.emit('execute', '$("#orderstatus").html(\'Done\');');
clearInterval(checkinterval);
}
});
});
}
var express = require('express');
var app = express();
var app = express();
var options = {
key: fs.readFileSync('privkey.pem'),
cert: fs.readFileSync('cert.pem'),
ca: fs.readFileSync("chain.pem")
};
var server = require('https').createServer(options, app);
var io = require('socket.io')(server);
var port = 443;
server.listen(port, function() {
console.log('Server listening at port %d', port);
});
io.on('connection', function(socket) {
socket.on('trackorder', function(userid) {
var checkinterval = setInterval(function() {
checkOrder(socket, userid, checkinterval);
}, 10000);
});
socket.on('disconnect', function() {
clearInterval(checkinterval);
});
});
Now I'm having issues on stopping the function if either the job is completed or the client disconnects.
How could I achieve that? I suppose the clearInterval() would work inside the function since it is passed but there is an issue with the on disconnect event handler. Either checkinterval is undefined or if I define it globally it stops the wrong function.
How can this be done properly?
Your checkInterval variable is out of scope when the disconnect event comes. You need to move its definition up a level.
io.on('connection', function(socket) {
// checkInterval variable is declared at this scope so all event handlers can access it
var checkInterval;
socket.on('trackorder', function(userid) {
// make sure we never overwrite a checkInterval that is running
clearInterval(checkInterval);
checkInterval = setInterval(function() {
checkOrder(socket, userid, checkInterval);
}, 10000);
});
socket.on('disconnect', function() {
clearInterval(checkinterval);
});
});
In addition:
I added a guard against overwriting the checkInterval variable if you ever get the trackorder event more than once for the same client.
You mispelled checkinterval in one place.
As others have said, polling your database on behalf of every single client is a BAD design and will not scale. You need to either use database triggers (so it will tell you when something interesting changed) or have your own code that makes relevant changes to the database trigger a change. Do not poll on behalf of every single client.
You have no error handling in either pool.getConnection() or connection.query().
Instead of that complicated setInterval stuff, just add a small IIFE that calls itself if the result isnt there yet. Some pseudocode:
function checkOrder(socket, userid){
//a variable pointing to the running timer
var timer;
//on error clear
socket.on("disconnect", ()=>clearTimout(timer));
//a small IIFE
(function retry(){
pool.getConnection(function(err, connection) {
//parse & notice socket
if (!alldone) //retry
timer = setTimeout(retry, 1000);
});
})();
}
I would say you're using a bad approach. You should go for push rather than pull.
What I mean is, emit the event when status of order changes. Don't put the burden on your database to hit it frequently for no reason.
On successful change of status, emit the event order_status_update with order id and what is the new status
socket.emit('order_status_update', {order_id: 57, status: 'In Process'});
This way you don't need any kind of loop or setinterval etc. No worries even if client is connected or not, its sockat.io business to take care of it. You will just raise the event.
I am brand new to socket.io and trying to create a sort of mmo using it. However I've been running into a few issues. The current one is if someone refreshes it doesn't look like it is disconnecting the previous socket? In my console it shows the user as having connected twice and will create another listening for the same user.
This is what my console looks like after 3 refreshes:
User has joined : a
in getMap fora
user has disconnected
User has joined : a
User has joined : a
in getMap fora
in getMap fora
in getMap fora
in getMap fora
user has disconnected
user has disconnected
User has joined : a
User has joined : a
User has joined : a
client side:
var socket = io();
setUsername();
function setUsername() {
socket.emit('add user', username);
}
socket.on('startMap', function (data) {
socket.emit('getMap');
});
server side:
io.on('connection', function (socket) {
var addedUser = false;
socket.on('add user', function (username) {
if (addedUser) return;
// we store the username in the socket session for this client
socket.username = username;
addedUser = true;
console.log("User has joined : " + socket.username)
socket.emit('startMap',{username:socket.username});
});//end add user
socket.on('getMap', function(){
console.log("in getMap for" + socket.username);
});
socket.on('disconnect', function(){
console.log("user has disconnected");
});
});
After 11 refreshes for same user:
(node:8808) Warning: Possible EventEmitter memory leak detected. 11 disconnect listeners added. Use emitter.setMaxListeners() to increase limit
(node:8808) Warning: Possible EventEmitter memory leak detected. 11 add user listeners added. Use emitter.setMaxListeners() to increase limit
(node:8808) Warning: Possible EventEmitter memory leak detected. 11 getMap listeners added. Use emitter.setMaxListeners() to increase limit
(node:8808) Warning: Possible EventEmitter memory leak detected. 11 updateMap listeners added. Use emitter.setMaxListeners() to increase limit
you have to listen to the disconnect event in the server side and broadcast accordingly
io.on('connection', function (socket) {
var addedUser = false;
socket.on('add user', function (username) {
if (addedUser) return;
// we store the username in the socket session for this client
socket.username = username;
addedUser = true;
console.log("User has joined : " + socket.username)
socket.emit('startMap',{username:socket.username});
});//end add user
socket.on('getMap', function(){
console.log("in getMap for" + socket.username);
});
socket.on('disconnect' , function(){
// your action on user disconnect
socket.broadcast.to(socket.chatroom).emit('user disconnect', name);
});
});
Check whenever user disconnected. Then do stuff there, remove from map etc.
let users = new Object(); // Store users
io.on('connection', function(socket) {
socket.on('disconnect', function() {
console.log("User has disconnected: " + users[socket.id])
delete users[socket.id]; // User disconnected, delete from list
});
socket.on('add user', function(username) {
users[socket.id] = username; // Add user to list
console.log("User has joined: " + username);
});
});
When i receive message from server ditConsumer based on flag i am invoking a function sendMessageToFile() that is working as expected, problem here is when i navigate to other page and come back again so here when message comes in socket.on is listening twice and executing all variables and method twice. if i go back and forth three time it will listen 3 times, I would appreciate help here, it looks like socket.io connection problem over multiple tabs.
ctrll.js
socket.on('ditConsumer',function (data) {
console.log('SEND MESSAGE FLAG',sendMessageFlag)
console.log('MESSAGE FROM SERVER',data);
var obj = {
file:$scope.filename,
data:data
}
$scope.event.push(data);
// socket.emit('messageToFile',obj);
if(sendMessageFlag === true) {
return sendMessageToFile(obj);
}
});
function sendMessageToFile (data){
if(data.file) {
socket.emit('startrecording', data);
$scope.disabledRecBtn = true;
$scope.disabledStopBtn = false;
$scope.showMessage = true;
}
}
socketFactory.js
angular.module('loggingApp').factory('socket', function($rootScope) {
'use strict';
var server = 'http://localhost:3000';
var socket = io.connect(server, {
'forceNew': true
});
return {
on: function(eventName, callback) {
socket.on(eventName, function() {
var args = arguments;
$rootScope.$apply(function() {
callback.apply(socket, args);
});
});
},
emit: function(eventName, data, callback) {
socket.emit(eventName, data, function() {
var args = arguments;
$rootScope.$apply(function() {
if (callback) {
callback.apply(socket, args);
}
});
})
}
};
});
serverIo.js
var sio = require('socket.io');
var ditconsumer = require('./consumers/ditconsumer');
var logsRecording = require('./records/logsRecording');
var io = null;
exports.io = function () {
return io;
};
exports.initialize = function(server) {
io = sio(server);
io.on('connection', function(socket) {
// Producer.startProducer();
ditconsumer.start(function(value){
io.emit('ditConsumer',value);
});
socket.on('createlogfile', function(params) {
logsRecording.userLogs(function(err,filename) {
if (err){
console.log(err);
} else {
socket.emit('filename', filename);
}
});
});
socket.on('startrecording', function(obj) {
logsRecording.recordLogs(obj);
});
socket.on('stopRecording',function (filename) {
console.log('stop recording data',filename);
logsRecording.deleteFile(filename);
});
});
};
One way would be to...
// add an event_id to the event message on server-side
io.sockets.emit('my_event', {msg:'hi', event_id: '1'});
// on client side keep track of handled events
var acknowledged = [];
// handle event
io.on('my_event', function (msg) {
// only continue if the event hasn't been handled before
if(!~acknowledged.indexOf(msg.event_id)){
// add to array of acknowledged events
acknowledged.unshift(msg.event_id);
// prevent array from growing to large
if(acknowledged.length > 20){
acknowledged.length = 20;
}
// handle once per event
}
});
You might also want to utilize socket.io rooms and create unique identifiers for ever every connection or create something like an uuid and store it in the localstorage space of the browser; read or create it and supply it when connection to io. Then when you send an event you could target specific rooms.
Or do something similar.....
I am looking at some node.js code that does push notification on a MySQL database.
http://www.gianlucaguarini.com/blog/push-notification-server-streaming-on-a-mysql-database/
There is a polling function.
var pollingLoop = function() {
// Doing the database query
var query = connection.query('SELECT * FROM users'),
users = []; // this array will contain the result of our db query
// setting the query listeners
query
.on('error', function(err) {
// Handle error, and 'end' event will be emitted after this as well
console.log(err);
updateSockets(err);
})
.on('result', function(user) {
// it fills our array looping on each user row inside the db
users.push(user);
})
.on('end', function() {
// loop on itself only if there are sockets still connected
if (connectionsArray.length) {
pollingTimer = setTimeout(pollingLoop, POLLING_INTERVAL);
updateSockets({
users: users
});
} else {
console.log('The server timer was stopped because there are no more socket connections on the app')
}
});
};
The particular code segment above that puzzles me is this;
updateSockets({
users: users
});
Why is the argument users: users?
The code for updateSockets() is here;
var updateSockets = function(data) {
// adding the time of the last update
data.time = new Date();
console.log('Pushing new data to the clients connected ( connections amount = %s ) - %s', connectionsArray.length , data.time);
// sending new data to all the sockets connected
connectionsArray.forEach(function(tmpSocket) {
tmpSocket.volatile.emit('notification', data);
});
};
{
users : users
}
This code is just a plain objet. The first users is the name of the object property and the second users is just a variable.
You can write like this if you want :
var myUsers = users;
updateSockets({
users: myUsers
});
It's an additional information stored in data
When this code performs emit(data), it sends the packet with parameters user and time (added in updateSockets)
It's the message you want to send
Problem with Promised Connections
I recently converted my Node app from running on my local machine to utilizing an Amazon EC2 for the Node app and a VPN for the file-serving and MySQL.
I learned just enough about Promises to write the following connection snippet (which runs 3 queries before responding to the client), utilizing Bluebird. The connections worked on my machine, but with the VPN hosted MySQL settings, the connections crashed every time, about 30 seconds after the app started, which I realized was probably because I'd forgotten to close them.
EDIT: Based on the comments, it appears the issue is not in the connection closures.
So I modified my script in the best way I knew to close the connections, but with Promises, this is confusing. This version of the connection doesn't work. It doesn't fail or cause any errors. It just returns no results on the server side. I think my problem is in the way I've closed the connections.
What's causing the issue?
Is it the connection closures?
If so, how would I close them properly?
My (Simplified) MySQL Connection Attempt with Bluebird Promises
var mysql = require('mysql');
var Promise = require('bluebird');
var moment = require('moment');
function createConnection() {
var connection = mysql.createConnection({
dateStrings : true,
host : 'hostname',
user : 'username',
password : 'password',
database : 'database'
});
connection = Promise.promisifyAll(connection);
return connection;
}
function sendGame(req, res, sales, settings, categories, players) {
var game = new Object();
game.sales = sales;
game.players = players;
game.settings = settings;
game.categories = categories;
var JSONgame = JSON.stringify(game);
console.log("Game: " + JSON.stringify(game, undefined, 4));
}
var retrieveSales = Promise.method(function (username, connection, timeFrame) {
console.log('User ' + username + ' retrieving sales...');
var q = 'select * from sales_entries where date BETWEEN ? AND ?';
return connection.queryAsync(q, timeFrame).then(function (results) {
return results[0];
});
});
var retrieveSettings = Promise.method(function (username, connection) {
console.log('User ' + username + ' retrieving settings...');
var q = 'select * from sales_settings';
return connection.queryAsync(q).then(function (results) {
return results[0];
});
});
var retrieveCategories = Promise.method(function (username, connection) {
console.log('User ' + username + ' retrieving categories...');
var q = 'select * from sales_categories';
return connection.queryAsync(q).then(function (results) {
return results[0];
});
});
var retrievePlayers = Promise.method(function (username, connection) {
console.log('User ' + username + ' retrieving players...');
var q = 'select * from users';
return connection.queryAsync(q).then(function (results) {
return results[0];
});
});
var gameSucceed = Promise.method(function gameSucceed(req, res) {
var username = req.body.username;
console.log('User ' + req.body.username + ' retrieving game...');
var timeFrame = [moment().days(0).hour(0).minute(0).second(0).format("YYYY-MM-DD HH:mm:ss"), moment().days(6).hour(0).minute(0).second(0).format("YYYY-MM-DD HH:mm:ss")];
//var connection = Promise.promisifyAll(createConnection());
return connection.connectAsync().then(function () {
console.log('Connection with the MySQL database openned for Game retrieval...');
return Promise.all([retrieveSales(username, connection, timeFrame), retrieveSettings(username, connection), retrieveCategories(username, connection), retrievePlayers(username, connection)]);
}).then(function () {
connection.end(),
console.log("...Connection with the MySQL database for Game retrieval ended")
});
});
function getGameData(req, res) {
gameSucceed(req, res).spread(function (sales, settings, categories, players) {
return sendGame(req, res, sales, settings, categories, players);
});
};
var req = new Object();
var res = new Object();
req.body = {
"username" : "user123",
"password" : "password"
}
getGameData(req, res);
Console Result
User user123 retrieving game...
Connection with the MySQL database openned for Game retrieval...
User user123 retrieving sales...
User user123 retrieving settings...
User user123 retrieving categories...
User user123 retrieving players...
...Connection with the MySQL database for Game retrieval ended
Game: {}
var gameSucceed = function gameSucceed(req, res) {
…
var connection = createConnection());
return connection.connectAsync().then(function () {
return Promise.all([…]);
}).then(function () {
connection.end();
});
};
The promise that is ultimately returned from this method does not have a resolution value. It is created by that then call from whose callback you do not return - which will lead to undefined. To fix this, just route the result through:
.then(function(results) {
connection.end();
return results;
});
However, if you do it like that the connection won't be closed in case of an error. The best solution is to use the finally() method, which just works like a finally clause in synchronous code. It's callback will be invoked both for resolutions and rejections, and the resulting promise will automatically carry on the value.
.finally(function() {
connection.end();
})
// .then(function(results) { })
Your code has a particular resource management problem like Bergi put it. You have to keep remembering when to close the collection and when not to.
The optimal solution would be to use Promise.using however, that's only available in the v2 branch of Bluebird so you're going to have to wait a while.
Until then, you can create your own wrapper method that does more basic scoped resource management:
function connect(fn,timeout){
timeout = (timeout === undefined) ? 8000 : timeout; // connection timeout
return createConnection().then(function(connection){
// run the function, when it resolves - close the connection
// set a 7 second timeout on the connection
return fn(connection).timeout(timeout).finally(function(){
connection.end();
});
});
}
Which would let you do:
connect(function(connection){
return gameSucceed(req,resp,connection); // connection is injected to that fn now
}).then(function(val){
// gameSucceed resolution value here
});
Now, when the gameSucceed is done, the connection will close itself automatically. This would make gameSucceed itself look like:
var gameSucceed = Promise.method(function gameSucceed(req, res,connection) {
var username = req.body.username;
console.log('User ' + req.body.username + ' retrieving game...');
var timeFrame = [moment().days(0).hour(0).minute(0).second(0).format("YYYY-MM-DD HH:mm:ss"), moment().days(6).hour(0).minute(0).second(0).format("YYYY-MM-DD HH:mm:ss")];
return connection.connectAsync().then(function () {
console.log('Connection with the MySQL database openned for Game retrieval...');
return Promise.all([retrieveSales(username, connection, timeFrame), retrieveSettings(username, connection), retrieveCategories(username, connection), retrievePlayers(username, connection)]);
}); // no longer its responsibility to handle the connection
});
Generally, you might also want to consider a more OOPish style of coding for your code.
Good luck, and happy coding.