I'm running into a strange issue where - in my production environment only (everything works fine in local testing) - socket.emit() works fine but io.emit() does not. In other words, each individual socket client connection can send and receive messages to the server, but when the server emits to all clients, none of them receive the message. This is strange, because it can see all the clients - if I check Object.keys(io.engine.clients) I see the ids of all connected clients. But io.emit() doesn't broadcast to any of them.
I'm using the latest version of node (7.7.4 and socket.io (1.7.3). I'm hosting on Heroku, and have enabled sticky sessions. I use cluster so that all CPUs are utilized, and I have a redis service set up to synchronize all the workers. All of that infrastructure appears to be working just fine.
Why would socket.emit() work for any given socket, but none of the other methods?
socket.on('sendChat', function(messageBundle) {
console.log('sockets: ' + Object.keys(io.engine.clients)) //shows clients
io.sockets.emit('incomingChat', messageBundle); //nothing
io.emit('incomingChat', messageBundle); //also nothing
var clients = Object.keys(io.engine.clients);
for (var i = 0; i < clients.length; i++) {
console.log('broadcasting to: ' + clients[i]);
socket.broadcast.to(clients[i]).emit('incomingChat', messageBundle); //still nothing
}
socket.emit('incomingChat', messageBundle); //this works though
});
UPDATE:
Here is where I define the socket stuff earlier.
var redis = require('redis');
var pub = redis.createClient(redisPort, redisHost, {
auth_pass: redisPwd
});
var sub = redis.createClient(redisPort, redisHost, {
detect_buffers: true,
auth_pass: redisPwd
});
var adapter = require('socket.io-redis');
io.adapter(adapter({
pubClient: pub,
subClient: sub
}));
var cluster = require('cluster');
var WORKERS = process.env.WEB_CONCURRENCY || 3;
if (cluster.isMaster) {
for (var i = 0; i < WORKERS; ++i)
console.log('forking process ' + i);
cluster.fork();
} else {
var express = require('express');
var session = require('express-session');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io').listen(server);
var port = process.env.PORT || 5000;
server.listen(port, function() {
console.log("Listening on " + port);
console.log("testing");
});
var mySession = session({...}) //this enables me to authenticate the socket using the same session middleware as the express routes
io.use(function(socket, next) {
mySession(socket.handshake, {}, next);
});
io.on('connection', function(socket) {
socket.emit('welcome');
console.log('socket connection');
socket.on(... etc.)
...})
...})
On the client side, the socket connection is initiated using a simple config:
var socket = io({
reconnection: true,
reconnectionDelay: 100,
});
io is undeclared on the beginning, and then You try to redeclare it in the else statement.
For me the issue was mismatching version of socket.io and reddis-adapter (upgraded socket io to 3.0.1 and redis adapter remained at 5.x.x)
Upgrading adapted to 6.0.1 solved it
Related
I want to use cluster on both side (Server and Client). I have got success on server side, but unable to do on client side. I am using node.js on both (Server side and Client side).
I am using below code on server side
var express = require('express'),
cluster = require('cluster'),
sio = require('socket.io');
var port = 3000,
num_processes = require('os').cpus().length;
if (cluster.isMaster) {
for (var i = 0; i < num_processes; i++) {
cluster.fork();
}
} else {
var app = new express();
var server = app.listen(port),
io = sio(server);
io.on('connection', function (client) {
client.on('evnt', function (data) {
console.log('evnt' + process.pid, data);
});
});
}
and on client side this one
var url = 'http://localhost:3000/';
var socket = require('socket.io-client')(url);
socket.on('connect', function () {
console.log('Connected with ', url);
setInterval(function () {
socket.emit('evnt', {sham: 'sakdf'});
}, 500)
});
socket.on('disconnect', function () {
console.log('Disconnected');
});
There is no need of cluster module on client side, simply we can create multiple process or child process if needed. We use cluster on server side because we have to bind multiple processes on same IP and port.
Socket IO server is running fine on single instance of NodeJs. But when I'm using cluster module of NodeJS to run the servers on multiple cores I'm getting the error, "Connection closed before receiving a handshake response". I've googled the reason and found out that,
Essence of the problem is, when you run multiple Node app threads (workers) on a server, or multiple servers, socket.io clients connections are routed by cluster in a random round-robin manner, and handshaken / authorized io client requests get handed to workers where they are not handshaken / authorized, where the mess begins. Source Link
I've tried a couple of things to make it work but no success so far. Here's the code
'use strict';
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var express = require('express');
var config = require('./config/environment');
var session = require('express-session');
var redisStore = require('connect-redis')(session);
var cluster = require('cluster');
var domain = require('domain');
var socketIo = require('./config/socketio');
var REDIS = require('redis')
var store = REDIS.createClient();
var pub = REDIS.createClient();
var sub = REDIS.createClient();
if(cluster.isMaster) {
var numWorkers = require('os').cpus().length;
for(var i = 0; i < numWorkers; i++) {
cluster.fork();
}
} else {
var d = domain.create ();
d.on ("error", function (error){
// start new server
});
// Setup server
var app = express();
var server = require('http');
d.run (function (){
server = server.createServer(app);
});
require('./config/express')(app);
require('./config/redis')();
require('./routes')(app);
server.listen(config.port, config.ip, function () {
console.log('Express server listening on %d, in %s mode', config.port, app.get('env'));
});
var redis = require('socket.io-redis');
var socketIO = require('socket.io')(server, {
serveClient: (config.env === 'production') ? false : true,
path: '/socket.io-client'
});
sub.subscribe('chat');
socketIO.adapter(redis( {host: 'localhost', port: 6379}));
socketIo.createSocketConnection('/dummy', socketIO, sub, pub, store);
exports = module.exports = app;
}
File: ./config/socketio
'use strict';
function addNamespaceForId (socketio, namespace, sub, pub, store){
socketio.of(namespace).on('connection', function(socket) {
onConnect(socketio, socket, namespace, sub, pub, store);
console.info('[%s] CONNECTED', socket.address);
sub.on('message', function(pattern, key){
store.hgetall(key, function(e, obj){
socket.send(obj.uid + ": " + obj.text)
})
})
socket.on('disconnect', function() {
console.info('[%s] DISCONNECTED', socket.address);
});
});
}
}
function onConnect(io, socket, namespace, sub, pub, store) {
socket.on('message', function(from, msg) {
store.incr("messageNextId", function(e, id){
store.hmset("messages:" + id, { uid: socket.sessionId, text: 'text;' }, function(e, r){
pub.publish("chat", "messages:" + id)
})
})
io.emit('broadcast', {
payload : from['message'],
source : from
});
io.of(namespace).emit('broadcast', {
payload : from['message'],
source : from
});
});
// When the client emits 'info', this listens and executes
socket.on('info', function(data) {
console.info('[%s] %s', socket.address, JSON.stringify(data, null, 2));
});
// Insert sockets below
require('../api/thing/thing.socket').register(socket);
}
module.exports = {
createSocketConnection : function (namespace, socketio, sub, pub, store){
addNamespaceForId(socketio, namespace, sub, pub, store);
}
};
I've also tried using adapter for redis as suggested in the documentation.
This setup works sometimes but not always. I'm unable to figure out the missing point.
I am trying to create a simple node.js server that will allow my socket based iOS app to send it's GPS coordinates to the server, and the server will broadcast the GPS coordinate to all connected iOS clients. Similarly, the clients are connected to the server using sockets. I tried using some sample code from Heroku's web server. CODE IS EDITED TO INCLUDE ANURAG'S ANSWER
var WebSocketServer = require("ws").Server
var http = require("http")
var express = require("express")
var app = express()
var port = process.env.PORT || 5000
app.use(express.static(__dirname + "/"))
var server = http.createServer(app)
server.listen(port)
console.log("http server listening on %d", port)
var wss = new WebSocketServer({server: server})
console.log("websocket server created")
var connectionList = [];
wss.on("connection", function(ws) {
console.log("connection");
connectionList.push(ws);
})
wss.on("message", function(data, id) {
var mes = server.unmaskMessage(data);
var str = server.convertToString(mes.message);
console.log(str);
var i;
for(i = 0; i < connectionList.lenth; i++) {
wss.sendMessage(one, str, connectionList[i]);
}
});
How do I modify this code to be able to receive messages from my app (via sockets) and then send that message to all other iOS clients connected. (The message is just a simple string)
BONUS QUESTION: Because Heroku makes you use it's environments port (rather than your own specified one), in my iOS app, when I connect to the server, would I just specify the Port that is printed to the console when the server is started.
Any help is appreciated, Thank you! :)
EDIT: For broadcasting to the clients, the code is:
wss.on('connection', function(ws) {
ws.on('message', function(message) {
wss.broadcast(message);
});
});
However how do I receive messages from a client, and how do I make the received message the message to be broadcasted to the other clients.
On getting the connection you need to store those connections.
Then you can send message to all those devices connect to your server using those connections.
You may try something like this:
var connectionList = [];
wss.on("connection", function(ws) {
connectionList.push(ws);
})
wss.on("message", function(data, id) {
var mes = server.unmaskMessage(data);
var str = server.convertToString(mes.message);
console.log(str);
var i;
for(i = 0; i < connectionList.lenth; i++) {
wss.sendMessage(one, str, connectionList[i]);
}
});
Read more here: https://www.npmjs.com/package/websocketserver
Here is the complete index.js code sothat the server brodcast received messages to clients:
var WebSocketServer = require("ws").Server
var http = require("http")
var express = require("express")
var app = express()
var port = process.env.PORT || 5000
app.use(express.static(__dirname + "/"))
var server = http.createServer(app)
server.listen(port)
console.log("http server listening on %d", port)
var wss = new WebSocketServer({server: server})
console.log("websocket server created")
wss.broadcast = function(data) {
for (var i in this.clients)
this.clients[i].send(data);
};
wss.on("connection", function(ws) {
console.log("websocket connection open");
ws.on('message', function(message) {
console.log("message received by server");
wss.broadcast(message);
})
ws.on("close", function() {
console.log("websocket connection close")
})
})
I'm using Node & Express 4.0 deployed on Heroku, and I'm trying to implement Socket.io with Redis as aa session store. So I have this as my current code:
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io').listen(server);
var RedisStore = io.RedisStore;
if (process.env.REDISTOGO_URL) {
// inside if statement
var rtg = require("url").parse(process.env.REDISTOGO_URL);
var redis = require("redis").createClient(rtg.port, rtg.hostname);
redis.auth(rtg.auth.split(":")[1]);
} else {
var redis = require("redis").createClient();
}
/** Initialize RedisStore for socket.io **/
io.set('store', new RedisStore({
redis : redis
}));
But I get the following error:
14:25:03 web.1 | io.set('store', new RedisStore({
14:25:03 web.1 | ^
14:25:03 web.1 | TypeError: undefined is not a function
I've also seen this way of defining a RedisStore:
var redis = require('socket.io/node_modules/redis');
var RedisStore = require('socket.io/lib/stores/redis');
However, my installed version of socket.io, installed using npm install --save socket.io, doesn't include stores in the lib directory:
EDIT
I saw this on the socket.io page in regards to their 1.0 release:
// 2. Implement the socket.io-redis adapter
var io = require('socket.io')(3000);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
But there's no other documentation I could find regarding this new module, and since I'm new to this whole stack, I don't think I could figure it out on my own.
The trend among node.js modules is to remove functionality that isn't truly core to the module.
Which is why socket.io 1.0 no longer supports redis out of the box.
So step one is to track down the functionality you need.
http://socket.io/docs/server-api/
https://github.com/Automattic/socket.io-adapter
https://github.com/Automattic/socket.io-redis
Then you need to install the other module npm install socket.io-redis --save
And finally configure your app.
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io').listen(server);
var redis = require('socket.io-redis');
io.adapter(redis(process.env.REDISTOGO_URL));
The nice part is the socket.io-redis adapter accepts redis urls and defaults to localhost:6379 so you (should) be able to simple pass in the REDISTOGO_URL
I had to parse libraries above to get this example, so I figured I would post a full blown example, but I must admit there are a couple of things off, this uses REDISCLOUD, it is on Heroku, it does work. I'll post this elsewhere and maybe put it in a doc too.
var redis = require('redis');
var ioredis = require('socket.io-redis'); //Adapter
var url = require('url');
var redisURL = url.parse(process.env.REDISCLOUD_URL );
var pub = redis.createClient(redisURL.port, redisURL.hostname, {return_buffers: true});
var sub = redis.createClient(redisURL.port, redisURL.hostname, {return_buffers: true});
pub.auth(redisURL.auth.split(":")[1]);
sub.auth(redisURL.auth.split(":")[1]);
var redisOptions = {
pubClient: pub,
subClient: sub,
host: redisURL.hostname,
port: redisURL.port
};
io.adapter(ioredis(redisOptions));
Following code works for me with Heroku Redis, hope this helps.
var app = express();
var server = require('http').Server(app);
var io = require('socket.io')(server);
var redis = require('redis');
var redisAdapter = require('socket.io-redis');
io.adapter(redisAdapter({
pubClient: redis.createClient(process.env.REDIS_URL, {return_buffers: true}),
subClient: redis.createClient(process.env.REDIS_URL, {return_buffers: true})
}));
For the ones interested,
this is my prototype chat server running on newest socket.io with express, multiple cores and redis as an intermediate.
Broadcasted messages are send to all room users no matter if they are connected to a different node and port instance.
Just run
node server.js
and on other machines
node client.js
or for testing node client.js 7001, 7002, 7003 ......
server.js
var options = {
//workers: 2, // total workers (default: cpu cores count).
first_port: 7000, // 8000, 8001 are worker's ports (default: 8000).
proxy_port: 5000, // default (5000).
session_hash: function (req, res) { return req.connection.remoteAddress; },
no_sockets: false // allow socket.io proxy (default: false).
};
require('sticky-socket-cluster')(options, start);
function start(port) {
// requirements
var express = require('express');
var http = require('http');
var socketio = require('socket.io');
var path = require('path');
var sticky = require('sticky-session');
var app = express();
var server = http.createServer(app);
var io = socketio.listen(server);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
server.listen(port, function() {
console.log(' - listening on ' + port+ ' ' + __dirname);
});
// require our chatserver
var ChatServer = require('./chatserver');
// initialize a new chat server.
new ChatServer({io: io, port: port}).init();
}
chatserver.js
RoomUtil = (function(){
roomMessages = {};
return {
getMessages : function(room_id,limit,cb){
//TODO
cb(roomMessages[room_id] || []);
},
postMessage : function(message,room_id,cb){
if (!roomMessages[room_id]) roomMessages[room_id] = [];
roomMessages[room_id].push(message);
cb();
}
}
})();
var Server = function(options) {
var self = this;
self.io = options.io;
// users array
self.users = [];
// initialize function
self.init = function() {
console.log("init");
// Fired upon a connection
self.io.on('connection', function(socket) {
console.log("incoming connection");
// var ru = new RoomUser();
self.handleConnection(socket,options.port);
});
}
// socket handler for an incoming socket
self.handleConnection = function(socket,port) {
// wait for a login message
socket.emit("incoming connection",{});
socket.on("joinroom",function(data,joinroom_callback){
console.log("attempt to join room ",data.room_id," on port ",port);
if (!data.room_id){
console.log("cannon join room -> no room id given");
return socket.disconnect();
}
else{
var room_id = data.room_id;
socket.join(room_id,function(){
console.log(socket.rooms);
RoomUtil.getMessages(data.room_id,50,function(messages){
console.log("client succesfully joined room ",data.room_id);
joinroom_callback(null,{'messages':messages});
});
socket.on("login",function(data,login_callback){
if (!data.username){
login_callback("invalid userdata",null);
}
else{
login_callback(null,1);
socket.on("post_message",function(data,message_callback){
if (!data.message || data.message == ""){
console.log("empty message posted. ignore");
message_callback("invalid_message",null);
}
else{
console.log("received message on port ",port,data.message);
message_callback(null,1);
RoomUtil.postMessage(data.message,room_id,function(){
RoomUtil.getMessages(room_id,50,function(messages){
console.log("emit messages to room id ",room_id);
//socket.to(room_id).emit('update_messages', messages);
//socket.broadcast.to(room_id).emit('update_messages', messages);
//socket.broadcast.to(room_id).emit('update_messages', messages);
//self.io.to(room_id).emit('update_messages', messages);
self.io.in(room_id).emit('update_messages', messages);
});
})
}
});
}
});
});
}
});
}
}
module.exports = Server;
client.js
var servercon = 'http://localhost:'+(process.argv[2] || 5000);
console.log("going to connect to "+servercon)
var socket = require('socket.io-client')(servercon);
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
socket.on('connect', function(){
console.log("connected, going to login");
socket.emit("joinroom",{"room_id":123123}, function(error,data){
if (error){
console.log("cannot join room ",error);
}
else{
console.log("succesfully joined room -> going to login now");
console.log("received messages count",data.messages.length);
socket.emit("login",{username:"John Mckain"}, function(error, message){
if (error){
console.log("error logging in ",error);
}
else{
console.log("logged in succesfully -> post message now");
var readline = function(){
rl.question("type in a message -> ", function(message) {
socket.emit("post_message",{'message':message}, function(error, message){
if (error){
console.log("error posting message");
readline();
}
else{
console.log("succesfully posted message");
readline();
}
});
});
}
readline();
}
});
socket.on("update_messages",function(data){
console.log("received new messages count ",data.length,data);
});
}
});
});
socket.on('event', function(data){
console.log("event send",data);
});
socket.on('disconnect', function(e){
console.log("disconnected",e);
});
socket.on("welcome",function(data){
console.log("on welcome ",data)
})
socket.on("pong",function(e){
console.log("pong")
})
I'm using node.js and socket.io. I'm using http-proxy to use port 80 on a machine that's running apache also. Apache is on a different IP. This all works great.
I added in Cluster and things got funky. It spawns two workers (dual core VM) as expected. But the connection on the client side, is off. Sometimes it's good, then if you disconnect, there's a delay reconnecting (there wasn't without the Cluster).
Here's the code I have.. any ideas why there's the delay between disconnect and connect using the cluster?
var http = require('http'),
httpProxy = require('http-proxy'),
io = require('socket.io'),
cluster = require('cluster'),
express = require('express'),
RedisStore = require('connect-redis')(express);
var numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
//Fork workers.
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('death', function(worker) {
console.log('worker ' + worker.pid + ' died');
});
} else {
//Server for workers
var app = express.createServer().listen(8585, '172.16.183.129');
app.configure(function(){
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser());
app.use(express.session({ secret: "secret", store: new RedisStore}));
app.use(app.router);
app.use(express.errorHandler({showStack: false, dumpExceptions: false}));
app.use(express.static(__dirname + '/public'));
});
//Create proxy server to use port 80
var proxy = httpProxy.createServer(8585, '172.16.183.129');
proxy.listen(80, '172.16.183.129');
//Initilize socket.io
var io = require('socket.io').listen(app, {origins: '*:*'});
io.sockets.on('connection', function(socket){
var test = setInterval(function() {
socket.emit('test', { result: numCPUs});
i++;
}, 900);
socket.on('disconnect', function () {
console.log('disconnect');
clearInterval(auctionTimer);
});
});
}
You need to add in a RedisStore so that the socket workers can work together. Note that the latest version of socket.io has a built in RedisStore that you probably should use instead of the connect-redis stuff.
socketio = require('socket.io')
var io = socketio.listen(app, {origins: '*:*'});
var host = 'redisserver'
var opts = {redisSub:{host:host},redisPub:{host:host},redisClient:{host:host}}
io.set('store', new socketio.RedisStore(opts))
As rbrc, you need some method for the different socket.io servers in your workers to communicate and share data, otherwise when you connect to one and then the other, they can't know that you already have a session. This goes for express' sessions too, and I see you use RedisStore there. Since you have a dependency on redis already, socketio.RedisStore seems like a good fit, otherwise you could try socket.io-clusterhub.