Here is the guide I'm basing my code off of: https://github.com/elad/node-cluster-socket.io
Although 9 threads are started, only one is in use at a time, and it's always the same thread that gets used. Their code works fine, but I've modified it to work with my program, and that's where I'm having issues.
manager.js:
var express = require('express'),
cluster = require('cluster'),
net = require('net'),
sio = require('socket.io'),
sio_redis = require('socket.io-redis');
var port = 3000,
num_processes = require('os').cpus().length;
if (cluster.isMaster) {
// This stores our workers. We need to keep them to be able to reference
// them based on source IP address. It's also useful for auto-restart,
// for example.
var workers = [];
// Helper function for spawning worker at index 'i'.
var spawn = function(i) {
workers[i] = cluster.fork();
// Optional: Restart worker on exit
workers[i].on('exit', function(code, signal) {
console.log('respawning worker', i);
spawn(i);
});
};
// Spawn workers.
for (var i = 0; i < num_processes; i++) {
spawn(i);
}
// Helper function for getting a worker index based on IP address.
// This is a hot path so it should be really fast. The way it works
// is by converting the IP address to a number by removing non numeric
// characters, then compressing it to the number of slots we have.
//
// Compared against "real" hashing (from the sticky-session code) and
// "real" IP number conversion, this function is on par in terms of
// worker index distribution only much faster.
var worker_index = function(ip, len) {
var s = '';
for (var i = 0, _len = ip.length; i < _len; i++) {
if (!isNaN(ip[i])) {
s += ip[i];
}
}
return Number(s) % len;
};
// Create the outside facing server listening on our port.
var server = net.createServer({ pauseOnConnect: true }, function(connection) {
// We received a connection and need to pass it to the appropriate
// worker. Get the worker for this connection's source IP and pass
// it the connection.
var worker = workers[worker_index(connection.remoteAddress, num_processes)];
worker.send('sticky-session:connection', connection);
}).listen(port);
} else {
// Note we don't use a port here because the master listens on it for us.
var server_local = require('./server.js');
server_local.startServer(0, function(server, io) {
// Here you might use middleware, attach routes, etc.
// Don't expose our internal server to the outside.
// Tell Socket.IO to use the redis adapter. By default, the redis
// server is assumed to be on localhost:6379. You don't have to
// specify them explicitly unless you want to change them.
io.adapter(sio_redis({ host: 'localhost', port: 6379 }));
// Here you might use Socket.IO middleware for authorization etc.
// Listen to messages sent from the master. Ignore everything else.
process.on('message', function(message, connection) {
if (message !== 'sticky-session:connection') {
return;
}
// Emulate a connection event on the server by emitting the
// event with the connection the master sent us.
server.emit('connection', connection);
connection.resume();
});
});
}
server.js:
var fs = require('fs');
var satJS = require('satellite.js');
var express = require('express');
var app = new express();
var serv = require('http').Server(app);
var sio = require('socket.io');
...
exports.startServer = function(port, callback) {
updateSatelliteData(function() {
server = app.listen(port);
io = sio(server);
initalize(io);
callback(server, io);
console.log("Server started");
});
}
function initalize(io) {
//Web stuff
app.get('/', function(req, res) {
res.sendFile(__dirname + '/client/index.html');
});
app.use('/client', express.static(__dirname + '/client'));
io.sockets.on('connection', function(socket) {
...
}
etc.
}
Related
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
I'm fairly new to node.js and recently started to make some modules. However I've come to a point where communication between modules is required. Since this is not a problem I've encountered in the past I'm stuck with finding a clean solution.
This is the boilerplate I currently got (Left out some checks to make the code a bit smaller). The basic idea atm is joining any irc channel given by an http post.
bot.js
//Include services
var Webservice = require('./Webservice');
var Ircservice = require('./Ircservice');
//Create service instances
var webservice = new Webservice();
var ircservice = new Ircservice();
//Initialize services
webservice.init(1337);
ircservice.init('alt-irc.snoonet.org', 80, 'User');
//Handle events
ircservice.on('irc-registered', function(msg){
console.log(ircservice.connected);
ircservice.joinChannel('#testchannel')
});
ircservice.on('irc-join', function(channel){
console.log('Successfuly joined: ' + channel);
});
webservice.on('web-join', function(streamer){
ircservice.joinChannel('#' + streamer);
});
Webservice.js
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
app.use(bodyParser.urlencoded({
extended: true
}));
var Webservice = function(){
EventEmitter.call(this);
};
Webservice.prototype.init = function(port){
app.listen(port, function () {
console.log('Webserver listening on ' + port);
});
this.initRoutes();
};
Webservice.prototype.initRoutes = function(){
var self = this;
//join a irc-channel
app.post('/join', function (req, res) {
var streamer = req.body.name;
self.emit('web-join', streamer);
res.send('Received')
});
};
util.inherits(Webservice, EventEmitter);
module.exports = Webservice;
Ircservice.js
var irc = require('irc');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var Ircservice = function(){
EventEmitter.call(this);
}
Ircservice.prototype.init = function(server, port, nick){
this.client = new irc.Client(server, nick, {
port: parseInt(port)
});
this.initListerners();
};
Ircservice.prototype.initListerners = function(){
var self = this;
this.client.addListener('message', function (from, to, message) {
console.log(from + ' => ' + to + ': ' + message);
});
this.client.addListener('join', function(channel, nick, message){
self.emit('irc-join', channel);
});
};
Ircservice.prototype.joinChannel = function(channel){
this.client.join(channel, null);
};
util.inherits(Ircservice, EventEmitter);
module.exports = Ircservice;
This example works perfectly, but as you can see the communication between my webservice and ircservice is handled by the bot.js. While this is perfectly fine for this example, I cannot use this method whenever I want.
Let say in the future I want to keep a list in my ircservice of all channels he has joined and display this through a webpage. I could keep a local array on my ircservice and on the join event add that channel to the array. But how do I continue on the webservice end. I can write an endpoint '/getchannels' but my webservice itself is not aware of the ircserver to get the channels (ircservice.getChannels or something similar) and firing an event in my web request doesn't feel like the way to go.
One solution that came up in my mind was passing the instances of the services to each other like webservice.setIrcservice(ircservice) and the other way around in the bot.js. But this feels like dirty code and a hard depency.
So how can I communicate between modules when I need data instantaneously and events are no option?
I have following nodejs code running on the server (chat engine). I want to convert this into a secure SSL/TLS based connection. How do i do that ?
In the client side (see below code), everytime i tried to convert this into SSL it gives me a error of "Cross origin request failed." i dont know why ?
NODE.JS Server Side Code
var cluster = require('cluster');
var net = require('net');
var fs = require('fs');
var config = require('./config.js');
var num_processes = 1;//require('os').cpus().length;
if (cluster.isMaster) {
// This stores our workers. We need to keep them to be able to reference
// them based on source IP address. It's also useful for auto-restart,
// for example.
var workers = [];
// Helper function for spawning worker at index 'i'.
var spawn = function (i) {
workers[i] = cluster.fork();
// Optional: Restart worker on exit
workers[i].on('exit', function (worker, code, signal) {
if (config.server.restart_instances_on_crash) {
spawn(i);
logging.log('debug', 'Node instances exited, respawning...');
}
});
};
// Spawn workers.
for (var i = 0; i < num_processes; i++) {
spawn(i);
}
// Helper function for getting a worker index based on IP address.
var worker_index = function (ip, len) {
var s = '';
for (var i = 0, _len = ip.length; i < _len; i++) {
if (ip[i] !== '.') {
s += ip[i];
}
}
return Number(s) % len;
};
/* wait 5 seconds to make sure all the instances are running and initialized */
setTimeout(function () {
// Create the outside facing server listening on our port.
var options = {
pauseOnConnect: true
};
var server = net.createServer(options, function (connection) {
// We received a connection and need to pass it to the appropriate
// worker. Get the worker for this connection's source IP and pass
// it the connection.
var str = connection.remoteAddress;
var ip = str.replace("::ffff:", '');
var worker = workers[worker_index(ip, num_processes)];
worker.send('sticky-session:connection', connection);
}).listen(config.server.listenport);
logging.log('debug', 'Server listening on ' + config.server.listenip + ':' + config.server.listenport + '...');
}, 5000);
process.on('uncaughtException', function (error) {
logging.log('error', 'uncaughtException');
logging.log('error',error.stack);
process.exit(1);
});
} else {
var express = require('express');
var sio = require('socket.io');
var sio_redis = require('socket.io-redis');
var dateandtime = require('./includes/dateandtime.js');
var stats = require('./includes/stats.js');
var helper = require('./includes/helper.js');
// Note we don't use a port here because the master listens on it for us.
var app = new express();
// Don't expose our internal server to the outside.
var server = app.listen(0, 'localhost'),
io = sio(server);
io.set('origins', '*:*');
}
In my Client side i have following code based socket.io JS module. (All these works fine without the SSL connection when using net module only)
Following is the client side javascript code to connect to the node server using socket.io functions.
var root_url = 'http://'+window.location.hostname;
var socket_io = 'http://example.com/js/chat/socket.io-1.3.5.js';
$(document).ready(function () {
$.getScript(socket_io, function () {
socket = io.connect(root_url + ':8440');
... etc
Can only hint you into checking the cors package for crossdomain requests but the deeper issue could be that not everything is running in SSL. (mixed http & https)
As i am running my server.js ,it starts of pretty well with the main staring page being loaded up but there i have to enter a nickname to enter any room. As i enter the name and click on the Create chat room button it shows this type of error. Since i am new to building node.js apps ,please help me to solve this issue..The server code is provided below..
TypeError: Arguments to path.join must be strings
at path.js:360:15
at Array.filter (native)
at exports.join (path.js:358:36)
at exports.send (/home/gauz/Desktop/hackview-master/node_modules/connect/lib/middleware/static.js:129:20)
at ServerResponse.res.sendfile (/home/gauz/Desktop/hackview-master/node_modules/express/lib/response.js:186:3)
at app.get.req.session.nick (/home/gauz/Desktop/hackview-master/server.js:71:9)
at callbacks (/home/gauz/Desktop/hackview-master/node_modules/express/lib/router/index.js:272:11)
at param (/home/gauz/Desktop/hackview-master/node_modules/express/lib/router/index.js:246:11)
at param (/home/gauz/Desktop/hackview-master/node_modules/express/lib/router/index.js:243:11)
at pass (/home/gauz/Desktop/hackview-master/node_modules/express/lib/router/index.js:253:5)
at Router._dispatch (/home/gauz/Desktop/hackview-master/node_modules/express/lib/router/index.js:280:5)
at Object.Router.middleware [as handle] (/home/gauz/Desktop/hackview-master/node_modules/express/lib/router/index.js:45:10)
at next (/home/gauz/Desktop/hackview-master/node_modules/connect/lib/http.js:204:15)
at next (/home/gauz/Desktop/hackview-master/node_modules/connect/lib/middleware/session.js:322:9)
at /home/gauz/Desktop/hackview-master/node_modules/connect/lib/middleware/session.js:341:9
at /home/gauz/Desktop/hackview-master/node_modules/connect/lib/middleware/session/memory.js:52:9
at process._tickCallback (node.js:415:13)
Server Code:
var express=require('express'),
app = express.createServer(),
sharejs = require('share'),
sharejsOptions={db:{type:'none'}};
/*
var express=require('express');
var app = express();
var sharejs = require('share');
var sharejsOptions={db:{type:'none'}};
*/
var env = process.env.NODE_ENV;
//if we were provided redis options, use them for persistence
//the if case is for developers not using redis
if(process.env.redis_port){
sharejsOptions.db= {
type: 'redis',
prefix: '',
port: process.env.redis_port,
auth: process.env.redis_auth || null
}
};
sharejs.server.attach(app, sharejsOptions);//attach to express
if (env !== 'production')
app.use(express.logger('dev'));
app.use(express.static(__dirname + '/public'));
app.use(express.favicon(__dirname+"/public/favicon.ico"));
app.use(express.cookieParser());
app.use(express.session({secret:"SuperSecretSessionKey"}));
//heroku support
var port = process.env.PORT || 8000;
app.listen(port);
console.log('App running on port : '+port);
//webRTC Stuff
var webRTC = require('webrtc.io').listen(app);
require('./rtc.js')(webRTC);
app.get('/', function(req, res) {
res.sendfile(__dirname + '/public/room.html');
});
/** Create a new random room */
app.get('/join',function(req,res){
var roomName=req.query.nickname.split('#')[1];
if(!roomName)
roomName=getRandomRoom();
var nickName = req.query.nickname.split('#')[0];
req.session.nick = nickName;
res.redirect('/room/'+roomName);
});
var getRandomRoom = function(){
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
var string_length = 8;
var randomstring = '';
for (var i=0; i<string_length; i++) {
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum,rnum+1);
}
return randomstring;
}
app.get('/room/:roomName',function(req,res){
if(req.session.nick || req.query.asknick){
//if a person has his nickname set, let him reach that
res.sendfile(__dirname+'/public/room.html');
}
else{
//make sure he/she is asked a username
res.redirect('/room/'+req.params.roomName+'?asknick=yes');
}
});
app.get('/setnick',function(req,res){
res.cookie('nick',req.query.nick);
req.session.nick = req.query.nick;
res.send('');
});
app.get('/debug',function(req,res){
res.json(req.session);
});
According to this GitHub issue, you'll need to replace calls to res.sendfile from this:
res.sendfile(__dirname+'/public/room.html');
to this:
res.sendfile('/public/room.html', { root: __dirname });
I'm looking to get Socket.io to work multi-threaded with native load balancing ("cluster") in Node.js v.0.6.0 and later.
From what I understand, Socket.io uses Redis to store its internal data. My understanding is this: instead of spawning a new Redis instance for every worker, we want to force the workers to use the same Redis instance as the master. Thus, connection data would be shared across all workers.
Something like this in the master:
RedisInstance = new io.RedisStore;
The we must somehow pass RedisInstance to the workers and do the following:
io.set('store', RedisInstance);
Inspired by this implementation using the old, 3rd party cluster module, I have the following non-working implementation:
var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
// Fork workers.
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
var sio = require('socket.io')
, RedisStore = sio.RedisStore
, io = sio.listen(8080, options);
// Somehow pass this information to the workers
io.set('store', new RedisStore);
} else {
// Do the work here
io.sockets.on('connection', function (socket) {
socket.on('chat', function (data) {
socket.broadcast.emit('chat', data);
})
});
}
Thoughts? I might be going completely in the wrong direction, anybody can point to some ideas?
Actually your code should look like this:
var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
// Fork workers.
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
var sio = require('socket.io')
, RedisStore = sio.RedisStore
, io = sio.listen(8080, options);
// Somehow pass this information to the workers
io.set('store', new RedisStore);
// Do the work here
io.sockets.on('connection', function (socket) {
socket.on('chat', function (data) {
socket.broadcast.emit('chat', data);
})
});
}
Another option is to open Socket.IO to listen on multiple ports and have something like HAProxy load-balance stuff.
Anyway you know the most important thing: using RedisStore to scale outside a process!
Resources:
http://nodejs.org/docs/latest/api/cluster.html
How can I scale socket.io?
How to reuse redis connection in socket.io?
Node: Scale socket.io / nowjs - scale across different instances
http://delicious.com/alessioaw/socket.io