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
Related
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.
}
I am currently working with socket.io swift client. Running on Iphone SE. this is the swift code
let socket = SocketIOClient(socketURL: URL(string: "http://example.com:4000")!, config: [.log(true), .forcePolling(true)]);
socket.connect();
socket.on("connect") {data, ack in
print("socket is connected");
socket.emit("getData", ["data": 3]);
}
And on the server:
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
io.on('connection', function(socket){
console.log('a user connected');
socket.on('disconnect', function(){
console.log('user disconnected');
});
socket.on('getData', function(result){
console.log(result);
});
});
app.listen(4000, function () {
console.log(' on at 4000!');
});
...And on the Xcode console, I get
2016-09-29 16:38:33.871895 proj[3070:1019256] LOG SocketEngine: Handshaking
2016-09-29 16:38:33.872301 proj[3070:1019256] LOG SocketEnginePolling: Doing polling request
2016-09-29 16:38:34.004312 proj[3070:1019256] LOG SocketEnginePolling: Got polling response
2016-09-29 16:38:34.004874 proj[3070:1019283] LOG SocketEngine: Got message: Cannot GET /socket.io/?transport=polling&b64=1
2016-09-29 16:38:34.005283 proj[3070:1019283] ERROR SocketIOClient: Got unknown error from server Cannot GET /socket.io/?transport=polling&b64=1
Which demonstrates a connection is made and the server is successfully found, but something else is wrong.
Would appreciate any help.
(Sidenote: If you don't need support for old browsers (or any browsers for that matter, since your client is a native mobile app) then you may consider using WebSocket which is an open standard. Socket.io is usually used to have a WebSocket-like functionality on browsers that don't support WebSocket. WebSocket on the other hand is an open standard, has a wide support (not only in browsers) and it has a better performance. See this answer for more details.)
Now, since you are already using Socket.io then here is how you can diagnose the problem. I would try to connect from a browser, which is a main way to connect with Socket.io, and see if that works. If it doesn't then it would mean that there's a problem in your server code. If it does then it could mean that there's a problem in your client. That would be the first thing to check. Going from there you can narrow the problem and hopefully fix it.
If you want to have a starting point with some working code using Socket.io, both server-site (Node.js) and client-side (browser vanilla JavaScript), then you can see the examples that I wrote originally for this answer, that are available on GitHub and on npm:
Socket.IO Server
Socket.IO server example using Express.js:
var path = require('path');
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', (req, res) => {
console.error('express connection');
res.sendFile(path.join(__dirname, 'si.html'));
});
io.on('connection', s => {
console.error('socket.io connection');
for (var t = 0; t < 3; t++)
setTimeout(() => s.emit('message', 'message from server'), 1000*t);
});
http.listen(3002, () => console.error('listening on http://localhost:3002/'));
console.error('socket.io example');
Source: https://github.com/rsp/node-websocket-vs-socket.io/blob/master/si.js
Socket.IO Client
Socket.IO client example using vanilla JavaScript:
var l = document.getElementById('l');
var log = function (m) {
var i = document.createElement('li');
i.innerText = new Date().toISOString()+' '+m;
l.appendChild(i);
}
log('opening socket.io connection');
var s = io();
s.on('connect_error', function (m) { log("error"); });
s.on('connect', function (m) { log("socket.io connection open"); });
s.on('message', function (m) { log(m); });
Source: https://github.com/rsp/node-websocket-vs-socket.io/blob/master/si.html
You can compare the same code with WebSocket versions:
WebSocket Server
WebSocket server example using Express.js:
var path = require('path');
var app = require('express')();
var ws = require('express-ws')(app);
app.get('/', (req, res) => {
console.error('express connection');
res.sendFile(path.join(__dirname, 'ws.html'));
});
app.ws('/', (s, req) => {
console.error('websocket connection');
for (var t = 0; t < 3; t++)
setTimeout(() => s.send('message from server', ()=>{}), 1000*t);
});
app.listen(3001, () => console.error('listening on http://localhost:3001/'));
console.error('websocket example');
Source: https://github.com/rsp/node-websocket-vs-socket.io/blob/master/ws.js
WebSocket Client
WebSocket client example using vanilla JavaScript:
var l = document.getElementById('l');
var log = function (m) {
var i = document.createElement('li');
i.innerText = new Date().toISOString()+' '+m;
l.appendChild(i);
}
log('opening websocket connection');
var s = new WebSocket('ws://'+window.location.host+'/');
s.addEventListener('error', function (m) { log("error"); });
s.addEventListener('open', function (m) { log("websocket connection open"); });
s.addEventListener('message', function (m) { log(m.data); });
Source: https://github.com/rsp/node-websocket-vs-socket.io/blob/master/ws.html
I hope this can help you evaluate whether staying with Socket.io or going with WebSocket is the right decision for you, and will give you some working client-side code to test your backend. The code is released under the MIT license (open source, free software) so feel free to use it in your project.
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
});
});
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)
sorry for posting this issue again, but most of the posts related don't answer my question.
i'm having issues to use multiple connections with the socket.io
i don't get the "socket.socket.connect" method to work, yet i get feedbacks from the first connection.
Here's my structure:
var iosocket = null;
var firstconnection = true;
var ip = "http://xxx.xxx.xxx"
var ipPort = 8081
function callSocket() {
iosocket = null;
iosocket = io.connect(ip,{port:ipPort,rememberTransport:true, timeout:1500});
if (firstconnection) {
firstconnection= false;
iosocket = io.connect(ip,{port:ipPort,rememberTransport:true, timeout:1500});
iosocket.on('connect', function () {console.log("hello socket");});
iosocket.on('message', function(message) {});//end of message io.socket
iosocket.on('disconnect', function () {console.log("disconnected");});
} else {
if (iosocket.connected === true) {
console.log("heyhey still connected");
iosocket.disconnect();
}
iosocket.socket.connect(ip,{port:ipPort,rememberTransport:true,timeout:1500});
}
};
it simply doesn't get any feedback from the second connection
i simply solved that IE8 bug by adding
<!DOCTYPE html>
at the top of the html
I think I know why this isn't working. For server-side code, this doesn't seem correct for socket.io. The connect method is used for clients and not servers. I think you are trying to make the server listen on a port. In that case, you should do:
var socket = require('socket.io');
var express = require('express');
var http = require('http');
var app = express();
var server = http.createServer(app);
var io = socket.listen(server);
io.on('connection', function (client) {
client.on('someEvent', function(someVariables){
//Do something with someVariables when the client emits 'someEvent'
io.emit('anEventToClients', someData);
});
client.on('anotherEvent', function(someMoreVariables){
//Do more things with someMoreVariables when the client emits 'anotherEvent'
io.emit('anotherEventToClients', someMoreData);
});
});
server.listen(8000);