node.js + socket.io broadcast from server, rather than from a specific client? - node.js

I'm building a simple system like a realtime news feed, using node.js + socket.io.
Since this is a "read-only" system, clients connect and receive data, but clients never actually send any data of their own. The server generates the messages that needs to be sent to all clients, no client generates any messages; yet I do need to broadcast.
The documentation for socket.io's broadcast (end of page) says
To broadcast, simply add a broadcast flag to emit and send method calls. Broadcasting means sending a message to everyone else except for the socket that starts it.
So I currently capture the most recent client to connect, into a variable, then emit() to that socket and broadcast.emit() to that socket, such that this new client gets the new data and all the other clients. But it feels like the client's role here is nothing more than a workaround for what I thought socket.io already supported.
Is there a way to send data to all clients based on an event initiated by the server?
My current approach is roughly:
var socket;
io.sockets.on("connection", function (s) {
socket = s;
});
/* bunch of real logic, yadda yadda ... */
myServerSideNewsFeed.onNewEntry(function (msg) {
socket.emit("msg", { "msg" : msg });
socket.broadcast.emit("msg", { "msg" : msg });
});
Basically the events that cause data to require sending to the client are all server-side, not client-side.

Why not just do like below?
io.sockets.emit('hello',{msg:'abc'});

Since you are emitting events only server side, you should create a custom EventEmitter for your server.
var io = require('socket.io').listen(80);
events = require('events'),
serverEmitter = new events.EventEmitter();
io.sockets.on('connection', function (socket) {
// here you handle what happens on the 'newFeed' event
// which will be triggered by the server later on
serverEmitter.on('newFeed', function (data) {
// this message will be sent to all connected users
socket.emit(data);
});
});
// sometime in the future the server will emit one or more newFeed events
serverEmitter.emit('newFeed', data);
Note: newFeed is just an event example, you can have as many events as you like.
Important
The solution above is better also because in the future you might need to emit certain messages only to some clients, not all (thus need conditions). For something simpler (just emit a message to all clients no matter what), io.sockets.broadcast.emit() is a better fit indeed.

Related

How many times can Socket.io 'connection' and 'disconnection' events be fired for single client?

EDIT: I see that I'm getting ping timeout and transport error reasons in my handler for disconnect on the server. This makes it difficult to maintain state in my server (I'm trying to keep track of which users are connected in a chat-like setup(. I was reading that it may be related to background tabs in Chrome (which I'm running). Does anyone have any experience with these 'spurious' disconnect events?
I'm new to Socket.io and am having some trouble understanding the connection and disconnection process.
As I understand it, the server receives the connection event once when a client connects, and one registers all the handlers for that client in the callback on on.('connection'). Is that true?
I want to maintain an of connected users, so I add a user to that array on the connection handler.
Should I then listen for the disconnect event to know when to remove a user from that array? Can I be guaranteed that that event will only be fired once?
It's a bit confusing, because on the client side, there is the connect event, which apparently can be fired multiple times -- the documentation says
// note: you should register event handlers outside of connect,
// so they are not registered again on reconnection
which is a different paradigm than on the server side, where all the handlers are registered inside the connection handler. But if the client-side connect event can fire on re-connection, what is the reconnect event for? (The docs says this event is "Fired upon a successful reconnection.")
In general I'm confused about the process of connection, disconnection and re-connection and how this relates to events, whether it happens "randomly" due to connection issues or only under the programmer's control, and how many times one should anticipate receiving each of these events -- once only for server, multiple times for client?
Thanks for any help!
I'm new to Socket.io and am having some trouble understanding the
connection and disconnection process.
Welcome to the wonderful world of Node.js + Socket.io It's super powerful!
As I understand it, the server receives the connection event once when
a client connects, and one registers all the handlers for that client
in the callback on on.('connection'). Is that true?
Correct. Take a look at this example of my code:
Server-side
var clients = []; /* stores all sockets on the fly */
io.on('connection', function (socket) {
clients[socket.id] = socket; /* keeps an array of sockets currently connected */
socket.on('disconnect', function (data) {
console.log(socket.id + " disconnected");
delete clients[socket.id];
});
});
Client-side
socket = io.connect(YOUR_SOCKET_URI, { transports: ['websocket'] } );
socket_delegates();
socket_delegates = function() {
// Socket events
socket.on('connect', function(data) {
/* handle on connect events */
});
socket.on('disconnect', function () {
/* handle disconnect events - possibly reconnect? */
});
socket.on('reconnect', function () {
/* handle reconnect events */
});
socket.on('reconnect_error', function () {
/* handle reconnect error events - possible retry? */
});
}
Should I then listen for the disconnect event to know when to remove a
user from that array? Can I be guaranteed that that event will only be
fired once?
Yes. You will see in the above server code that we listen for disconnect and then do what we need to.
Nothing should be random. You should have code in place to handle the connect, disconnect on the server side and code to handle the connect, disconnect and reconnect on the client side.

I can't get my head around websockets (via socket.io and node.js)

I'm new to websockets/socket.io/node.js. I'm trying to write a card game app, but pretty much all the example tutorials I've found are creating chat applications. So I'm struggling to get my head around the concepts and how they can be applied to my card game.
Keeping it simple, the card game will involve two players. The game involves moving cards around the table. Each player has to see the other player's moves as they happen (hence the need for constant connections). But the opponents cards are concealed to the other.
So two people browse to the same table then click to sit (and play, when both seats are taken). Using
io.on("connection", function(sock){
//socket events in here
});
am I creating the one socket ('io', or 'sock'?) that both clients and the server share, or is that two separate sockets (server/clientA and sever/clientB)? I ask, because I'm struggling to understand what's happening when a message is emitted and/or broadcast. If a client emits a message, is that message sent to both the server and the other client, or just the server? And then, further does it also send the message to itself as well?? It seems as though that's the logic... or what is the purpose of the 'broadcast' method?
From a functional perspective, I need the server to send different messages to each player. So it's not like a chatroom where the server sends the chat to everyone. But if it's one socket that the three of us share (clients and server), how do I manage who sees what? I've read about namespaces, but I'm struggling to work out how that could be used. And if it's two separate sockets, then I can more easily imagine sending different data to the separate clients. But how is that implemented - is that two 'io' objects, or two 'sock' objects?
Finally, I've got no idea if this is the sort of long-winded question that is accepted here, so if it's not, can someone direct me to a forum that discussions can occur? Cheers!
(in case it matters I'm also using Expressjs as the server).
Edit to add:
Part of my confusion is regarding the difference between 'io' and 'sock'. This code eg from the socket.io page is a good example of methods being applied to either of them:
io.on('connection', function(socket){
socket.emit('request', /* */); // emit an event to the socket
io.emit('broadcast', /* */); // emit an event to all connected sockets
socket.on('reply', function(){ /* */ }); // listen to the event
});
WebSocket server side listens for incoming socket connections from clients.
Each client upon connection opens its own socket between him and server. The server is the one that keeps track of all clients.
So once client emits the message server is listening for, the server can do with that message whatever. The message itself can contain information about who is the recipient of that message.
The server can pass the message to everyone or broadcast it to specific user or users based on information your client has sent you or some other logic.
For a card game:
The server listens for incoming connections. Once two clients are connected both of them should emit game ID in which they want to participate. The server can join their sockets in one game(Room) and all of the communication between those two clients can continue in that room. Each time one of the clients passes data to the server, that data should contain info about the recipient.
Here is one simple example that could maybe get you going:
Client side
// set-up a connection between the client and the server
var socket = io.connect();
// get some game identifier
var game = "thebestgameever";
socket.on('connect', function() {
// Let server know which game you want to play
socket.emit('game', game);
});
function makeAMove(move)
{
socket.emit('madeAMove', {move:move, game:game});
}
socket.on('move', function(data) {
console.log('Player made a move', data);
});
Server side
io = socketio.listen(server);
//listen for new connections from clients
io.sockets.on('connection', function(socket) {
// if client joined game get his socket assigned to the game
socket.on('game', function(game) {
socket.join(game);
});
socket.on('madeAMove', function(data){
let game = data.game;
let move = data.move;
io.sockets.in(game).emit('move', move);
});
})

Socket.IO Emit is emitting to all clients

So I'm writing an app in NodeJS, and to preface my question please understand how my setup currently works:
I have Clients who connect via Socket.io to "Server X", and then my "Server X" connects via Socket.io to "Server Y", and they all send data back to each other via Socket.io.
Client <---> Server X <---> Server Y
Clients refer to users and their browsers, and then I have a node app running on both my server x and my server y.
So in the code below on my Server X, if you look at line 4 it works exactly as it should. It emits the message ONLY to the client who requested it.
io.on('connection', function(socket){
// This works just fine.
socket.emit('return_login', 'Test Message');
socket.on('login', function(data){
// This line correctly sends the data to Server Y
server_y.emit('login', data);
});
server_y.on('return_login', function(data){
// This emits to all connected clients???
socket.emit('return_login', data);
});
});
Now my problem is when "Server Y" emits return_login to server x, what I want to happen is for server x to take the value emitted by server y and just send it back to the original client, or web browser. But for some reason that line emits to ALL connected clients.
I have a laptop, this computer and my phone all testing this and every time that emit happens it sends to EVERYONE.
If someone could please help me with this I would greatly appreciate it. If I need to post more code please let me know.
I am not sure about your code. But I usually use room to emit to an user and callback function of socket. This is my solution using callback instead of return_login event
io.on('connection', function(socket){
socket.on('login', function(data, callback){
// Using callback instead of return_login event
server_y.emit('login', data, function(responseData){
// put this socket to room as name = user_id
socket.join(responseData.user_id);
// return result by callback
callback(responseData)
});
});
});
// emit to exactly user_id with return_login event if you want
io.to(user_id).emit('return_login', {key: 'ok'})
It is sending to all clients because you have installed a separate listener for each socket for the return_login message. So, when one client logs in and the return_login message is sent back to your server, you have a separate listener for every single socket that sees that message and forwards it to that socket. In this way, it gets sent to every connected socket.
One way to fix that is to make sure that the return_login message is only sent to the socket that it belongs to. If you can send a socket.id with that message and have that server echo that id back as part of the response, then you can check that when you receive the message to make sure you only send it to the socket that it belongs to.
This is one of the issues with a pure message-based system. You are trying to do a request/response where only the requester sees the response, but socket.io isn't a request/response system. A response is sent to all listeners of that particular message and since you have a listener for that message for every single socket that is connected, every single socket is seeing it and then forwarding it on to it's client.
So, with a corresponding modification to the other server to echo back the id value, you could do this:
io.on('connection', function(socket){
// This works just fine.
socket.emit('return_login', 'Test Message');
socket.on('login', function(data){
// add our id so we can identify our response back
data.id = socket.id;
server_y.emit('login', data);
});
let fn = function(data) {
// check to see if this message is destined for this socket
if (data.id === socket.id) {
// make data copy with id value removed so
// we don't send that to the client
let tempData = Object.assign({}, data);
tempData.delete(id);
socket.emit('return_login', tempData);
// we're done with this listener so remove it
server_y.removeListener('return_login', fn);
}
});
server_y.on('return_login', fn);
});
It might be tempting to just remove your listener for the return_login message after you receive it, but that causes a race condition if two clients happen to both be in the process of logging in at the same time and thus both have listeners at the same time, then the first message would be received by both listeners.

Attaching event emitter to socket.connected

I am currently writing an Electron app, for which in the main process I want to talk to a server over websockets.
Part of my main process' code base depends on the user's socket connection status. I require the socket.io-client library
const socket = require('socket.io-client')(host);
which gives me access to the variable socket.connected, which is true or false according to whether a connection is established to the server.
Thus, what I would like is to attach an event emitter to this variable. I have made it to work with polling
var events = require('events').EventEmitter;
var event = new events();
// Emits successfully every 200ms 'Connection: true|false'
event.on('status', function(status){
console.log('Connection: ', status);
});
setInterval(function() {
let status = socket.connected;
event.emit('status', status);
}, 200);
but was wondering whether this truly is the way to implement it. To me it seems strange to have to resort to polling in an async framework like nodejs. On the other, I could not find other ways to implement. Best-case scenario would be to attach somehow an event emitter directly to socket.connected, but was unable to find how to do that. Could anybody advise me on a better way to implement?
Thanks
You can get notified of the completion of a client connection with the connect event:
socket.on('connect', function() {
// client socket is now connected to the server
});
socket.on('disconnect', function() {
// client socket is now disconnected from the server
});
Documentation for client events here: http://socket.io/docs/client-api/#manager(url:string,-opts:object). There are other events in that doc if you also want to see other things like a reconnect.

nodejs push notification that subscribes to redis

For logged in users only, I want to somehow notify them if they have any e.g. new notifications.
For example, say a member has sent them a private message, I want to tell the user that they have a new message to view (assuming they have not refreshed the page).
With Nodejs and redis, how would I go about doing this?
Note: I only need nodejs to send a small json to the user saying they have a new message.
The workflow is as follows that I was thinking:
1. user is logged in, a new message is sent to them.
2. somehow using nodejs and redis and long-polling, nodejs communicates back to the logged in users browser they have a pending message.
3. when nodejs sends this push notification, I then call another javascript function that will call a rest service to pull down additional json with the message.
I am integrating nodejs into an existing application, so I want to keep it as simple as possible with nodejs responsible for only notifying and not doing any additional logic.
Can someone outline how I should get going with this?
Should I be using redis http://redis.io/topics/pubsub somehow?
I'm not really sure how that works even after reading the page about it.
If you are integrating your node service into an existing application I would rather use some sort of messaging system to communicate messages from that application to node instead of a DB, even an in-memory DB. For clarity, I will assume you can use rabbitmq. If you do need to use redis, you will just need to find a way to use its publishing instead of rabbitmq publishing and corresponding node-side subscription, but I would imagine that the overall solution would be identical.
You need the following modules:
rabbitmq server (installation complexity about the same as for redis)
rabbitmq library in your external application to send messages, most languages are supported
rabit.js module for node to subscribe to messages or to communicate back to the external application
socket.io module for node to establish real-time connection between the node server and clients
I will also assume that both your external application and your node server have access to some shared DB (which can be redis), where node client session information is stored (e.g. redis-session-store for node). This would allow to use sessionId to validate who the message is for, if the user in the session is logged in and if certain users need to be sent notifications at all (by an external app).
This is how your stack might look like (unpolished):
Define a publisher in node to notify your external application that it needs to start/stop sending messages for a given sessionId. I will assume that for a given sessionId the user information can be recovered on either side (node or external application) from the shared DB and the user can be validated (here for simplicity by checking session.authenticated_user). Also define a subscriber to listen to incoming messages for the users:
var context = require('rabbit.js').createContext();
var pub = context.socket('PUB');
var sub = context.socket('SUB');
Define a socket.io connection(s) from your node server to the clients. As soon the client's web page is (re)loaded and io.connect() is called the below code will be executed (see clinet side at the end of the answer). As a new connection is established, validate the user is logged in (meaning its credentials are in the session), register the socket handler and publish a notification to the external application to start sending messages for this sessionId. The code here assumes a page reload on login/logout (and thus new socket.io session). If this is not the case, just emit a corresponding socket.io message from the client to node and register a handler in the method below in the same way as it is done for a new connection (this is beyond the scope of this example):
var sessionStore = undefined; // out-of-scope: define redis-session-store or any other store
var cookie = require("cookie"),
parseSignedCookie = require('connect').utils.parseSignedCookie;
// will store a map of all active sessionIds to sockets
var sockets = {};
// bind socket.io to the node http server
var io = require('socket.io').listen(httpServer);
// assumes some config object with session secrect and cookie sid
io.sockets.on("connection", function(socket) {
if (socket.handshake.headers.cookie) {
var cks = cookie.parse(socket.handshake.headers.cookie);
var sessionId = parseSignedCookie(cks[config.connectSid], config.sessionSecret);
// retrieve session from session store for sessionId
sessionStore.get(sessionId, function(err, session) {
// check if user of this session is logged in,
// define your elaborate method here
if (!err && session.authenticated_user) {
// define cleanup first for the case when user leaves the page
socket.on("disconnect", function() {
delete sockets[sessionId];
// notify external app that it should STOP publishing
pub.connect('user_exchange', function() {
pub.write(JSON.stringify({sessionId: sessionId, action: 'stop', reason: 'user disconnected'}), 'utf8');
});
});
// store client-specific socket for emits to the client
sockets[sessionId] = socket;
// notify external app that it should START publishing
pub.connect('user_exchange', function() {
pub.write(JSON.stringify({sessionId: sessionId, action: 'start'}), 'utf8');
});
}
});
}
});
Connect subscriber to the rabbitmq exchange to catch messages and emit them to clients:
sub.connect('messages_exchange', function() {
sub.on("readable", function() {
// parse incoming message, we need at least sessionId
var data = JSON.parse(sub.read());
// get socket to emit for this sessionId
var socket = sockets[data.sessionId];
if (socket) {
socket.emit("message", data.message);
} else {
// notify external app that it should STOP publishing
pub.connect('user_exchange', function() {
pub.write(JSON.stringify({sessionId: sessionId, action: 'stop', reason: 'user disconnected'}), 'utf8');
});
// further error handling if no socket found
}
});
});
Finally your client will look roughly like this (here in Jade, but that's just because I already have this whole stack along these lines):
script(src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js")
script(src="/socket.io/socket.io.js")
script(type='text/javascript').
$(function(){
var iosocket = io.connect();
iosocket.on('connect', function () {
// do whatever you like on connect (re-loading the page)
iosocket.on('message', function(message) {
// this is where your client finally gets the message
// do whatever you like with your new message
});
});
// if you want to communicate back to node, e.g. that user was logged in,
// do it roughly like this
$('#btnSend').click(function(event) {
iosocket.send('a message back to the node server if you need one');
});
});
Here is also a really nice explanation from Flickr on how they created a highly available and scalable push notification system with NodeJS and Redis.
http://code.flickr.net/2012/12/12/highly-available-real-time-notifications/

Resources