113

I am trying to use sockets with node.js, I succeded but I don't know how to differentiate clients in my code. The part concerning sockets is this:

var WebSocketServer = require('ws').Server, 
    wss = new WebSocketServer({port: 8080});
wss.on('connection', function(ws) {
    ws.on('message', function(message) {
        console.log('received: %s', message); 
        ws.send(message);
    });
    ws.send('something');
});

This code works fine with my client js.

But I would like to send a message to a particular user or all users having sockets open on my server.

In my case I send a message as a client and I receive a response but the others user show nothing.

I would like for example user1 sends a message to the server via webSocket and I send a notification to user2 who has his socket open.

giavac
  • 988
  • 7
  • 22
Ajouve
  • 9,735
  • 26
  • 90
  • 137
  • How do _you_ define which connection is "user1" and which is "user2"? – lanzz Nov 13 '12 at 16:10
  • I don't know realy, I thought to give a kind of socketSession to the user when connecting – Ajouve Nov 13 '12 at 16:16
  • 4
    Well, as soon as you know the _user identity_ of the connection (e.g. when your user sends his username as a message), you can store a named reference to it in a dictionary (e.g. `connections[username] = ws`) and after that, elsewhere, you can do something like `connections[username].send(message)` – lanzz Nov 13 '12 at 16:19
  • If your need is not quite to target an individual user, but rather a group of users (could be a group of 1) silo'ed into "rooms", you could use the socket `join()` and `broadcast()` methods. See some discussion here: http://stackoverflow.com/questions/6846174/dynamic-rooms-with-socket-io-and-node – meetamit Nov 13 '12 at 16:23
  • Thanks It works fine registring all my ws in an array :) – Ajouve Nov 13 '12 at 16:31

13 Answers13

138

In nodejs you can directly modify the ws client and add custom attributes for each client separately. Also you have a global variable wss.clients that can be used anywhere. Please try the following code with at least two clients connected:

var WebSocketServer = require('ws').Server;
var wss = new WebSocketServer({
    server: httpsServer
});


wss.getUniqueID = function () {
    function s4() {
        return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
    }
    return s4() + s4() + '-' + s4();
};

wss.on('connection', function connection(ws, req) {
    ws.id = wss.getUniqueID();

    wss.clients.forEach(function each(client) {
        console.log('Client.ID: ' + client.id);
    });
});

You can also pass parameters directly in the client connection URL:

https://myhost:8080?myCustomParam=1111&myCustomID=2222

In the connection function you can get these parameters and assign them directly to your ws client:

wss.on('connection', function connection(ws, req) {

    const parameters = url.parse(req.url, true);

    ws.uid = wss.getUniqueID();
    ws.chatRoom = {uid: parameters.query.myCustomID};
    ws.hereMyCustomParameter = parameters.query.myCustomParam;
}
ᴍᴇʜᴏᴠ
  • 4,804
  • 4
  • 44
  • 57
Jzapata
  • 2,442
  • 1
  • 12
  • 9
  • Is there a way to do this without mutating `ws`? When doing `ws.clients.values()`, is there a way to grab the request off of that list? – Kevin Ghadyani Mar 07 '18 at 01:13
  • 2
    +1 for the answer. Adding references supporting it: https://github.com/websockets/ws/issues/859 – Maoritzio Mar 05 '19 at 20:16
  • This is was awesome, thanks! I am using the websocket library (instead of ws) so I had to use `req.resourceURL` This includes a parsed querystring as part of the object so you can just `req.resourceURL.query.myCustomParam` – Chris Broski Jun 22 '20 at 15:16
  • 1
    In TypeScript, I get a `Property 'id' does not exist on type 'WebSocket'` error. Is there a way to do this without casting to `any` as described [here](https://stackoverflow.com/questions/18083389/ignore-typescript-errors-property-does-not-exist-on-value-of-type)? – Omar Sharaki Nov 17 '20 at 10:07
  • If any newbie is wondering, `url` at `url.parse()` is a built-in Nodejs module. In order to get this line to work add `const url = require('url')` to your script – A__ Sep 07 '22 at 19:33
70

You can simply assign users ID to an array CLIENTS[], this will contain all users. You can directly send message to all users as given below:

var WebSocketServer = require('ws').Server,
    wss = new WebSocketServer({port: 8080}),
    CLIENTS=[];

wss.on('connection', function(ws) {
    CLIENTS.push(ws);
    ws.on('message', function(message) {
        console.log('received: %s', message);
        sendAll(message);
    });
    ws.send("NEW USER JOINED");
});

function sendAll (message) {
    for (var i=0; i<CLIENTS.length; i++) {
        CLIENTS[i].send("Message: " + message);
    }
}
dgil
  • 2,318
  • 2
  • 23
  • 39
Ankit Bisht
  • 1,209
  • 12
  • 14
  • 1
    if you send clients' unique ID back and forth, you can also get the source client of the message. – Mehmet AVŞAR Sep 17 '14 at 08:34
  • 47
    This solution cannot be scaled because variable CLIENTS lives inside a single process of NodeJS. Even if we harness multiple cores by forking multiple processes, CLIENTS is not a shared memory allocation, each process maintains its own list of CLIENTS. Hence, it would require subsequent connections to talk to the same process. – Khanh Hua Nov 21 '15 at 00:17
  • 1
    could you post client side as well? – johannesMatevosyan Feb 19 '16 at 14:53
  • 13
    @KhanhHua could you please provide a scalable solution – vgoklani Apr 27 '16 at 10:57
  • @Khanh Hua I absolutely agree. I'm struggling with this right now. Can anyone suggest how this might be implemented so multiple workers in a cluster could all share a collection of web sockets and act upon them? – Will Brickner Nov 28 '16 at 18:44
  • 1
    I'm thinking of using REDIS to store web socket objects or something but I don't know if that's a half baked idea or not. – Will Brickner Nov 28 '16 at 18:48
  • 1
    Cache systems like Redis can only flattened store structures, for example connection ID or handle ID of connected clients. When a client connects, a socket representing the client comes to exist in the process of the server. When the process dies, socket is closed, connection is broken. This client socket cannot be shared among processes (correct me if I am wrong). – Khanh Hua Nov 29 '16 at 09:28
  • How would you get the clients information, such as their IP address? – Native Coder Nov 29 '16 at 22:54
  • 9
    You can use Redis pub-sub and make a "publish" channel which every server you have listens to. Whenever a message is published on that channel, every server broadcasts to all of their clients. Simple as that. – Nepoxx Jan 03 '17 at 15:26
  • 8
    Per [the docs](https://github.com/websockets/ws/blob/master/doc/ws.md), `WebSocketServer` has a `clients` property that tracks current clients for you. The docs also give an example of iterating over that collection to send a broadcast message to each client. – Coderer Jul 20 '17 at 08:40
  • Simple test case this would fail. If user has multiple devices connected at the same time, you'd only be able to send message to a single device. – cyberrspiritt Nov 14 '17 at 10:11
  • @MehmetAVŞAR, how can we send back the assigned ID to client? – Lamar Mar 28 '18 at 07:35
  • @KhanhHua SO is not for providing full-scale solutions but rather short snippets of code to help you in the right direction. What you are talking about is not related to the question asked. That is a different subject entirely. – Emobe Mar 03 '19 at 14:28
  • 1
    This solution will lead to memory leak eventually. – link89 Nov 01 '21 at 11:28
  • @link89 Hi could you please elaborate? Thank you! – ᴍᴇʜᴏᴠ May 09 '22 at 06:27
  • 1
    @mehov It keeps pushing new `ws` instance to `CLIENTS` without removing them at some point, which will exhaust the memory after handle too many connections. – link89 May 10 '22 at 14:37
17

you can use request header 'sec-websocket-key'

wss.on('connection', (ws, req) => {
  ws.id = req.headers['sec-websocket-key']; 

  //statements...
});
Serhat Ates
  • 885
  • 1
  • 12
  • 18
11

This code snippet in Worlize server really helped me a lot. Even though you're using ws, the code should be easily adaptable. I've selected the important parts here:

// initialization
var connections = {};
var connectionIDCounter = 0;

// when handling a new connection
connection.id = connectionIDCounter ++;
connections[connection.id] = connection;
// in your case you would rewrite these 2 lines as
ws.id = connectionIDCounter ++;
connections[ws.id] = ws;

// when a connection is closed
delete connections[connection.id];
// in your case you would rewrite this line as
delete connections[ws.id];

Now you can easily create a broadcast() and sendToConnectionId() function as shown in the linked code.

Hope that helps.

FirstVertex
  • 3,657
  • 34
  • 33
  • 2
    A `for in` loop will need to be used to broadcast, as opposed to a standard for loop, as there are "holes" in the (associative) array. – Tobiq Aug 12 '17 at 22:48
8

It depends which websocket you are using. For example, the fastest one, found here: https://github.com/websockets/ws is able to do a broadcast via this method:

var WebSocketServer = require('ws').Server,
   wss = new WebSocketServer({host:'xxxx',port:xxxx}),
   users = [];
wss.broadcast = function broadcast(data) {
wss.clients.forEach(function each(client) {
  client.send(data);
 });
};

Then later in your code you can use wss.broadcast(message) to send to all. For sending a PM to an individual user I do the following:

(1) In my message that I send to the server I include a username (2) Then, in onMessage I save the websocket in the array with that username, then retrieve it by username later:

wss.on('connection', function(ws) {

  ws.on('message', function(message) {

      users[message.userName] = ws;

(3) To send to a particular user you can then do users[userName].send(message);

droid-zilla
  • 536
  • 7
  • 8
  • could you provide any link to code where you include username? – johannesMatevosyan Feb 21 '16 at 07:10
  • On the client, when you send a message you can create a JSON object then stringify it like so: var sendObject = {userName:"Tim",pm:true,message:"how are you?"}; ws.send(JSON.stringify(sendObject)); Also, in Einaros you can send a json payload in the headers, like so: WS.open(uri, [JSON.stringify({userName:"Tim"})]); Then you can parse it on the server using ws.upgradeReq.headers and can turn the string back into its object via JSON.parse. I have used this method to create user groups, but it can also be used to store a user array. – droid-zilla Feb 22 '16 at 19:14
  • I have a similar issue but could not solve it, could you have a look? http://stackoverflow.com/questions/35535700/websockets-send-messages-and-notifications-to-all-clients-except-sender – johannesMatevosyan Feb 23 '16 at 09:50
  • @johannesMatevosyan If you think my answer is correct could you please upvote. Thanks. I have commented on your plunk and found one spelling error that might be affecting things. – droid-zilla Feb 23 '16 at 18:41
  • sure I will, first I need to check it tommorow. – johannesMatevosyan Feb 23 '16 at 18:52
  • Show us how & where you push to users[], otherwise, this answer doesn't answer the question. It just shows how to broadcast to all clients, not individual ones. -1 – Spankied Aug 11 '21 at 08:40
6

I'm using fd from the ws object. It should be unique per client.

var clientID = ws._socket._handle.fd;

I get a different number when I open a new browser tab.

The first ws had 11, the next had 12.

  • 13
    I think the file descriptor, fd, could be reused if a user leaves and another user joins... Essentially they might get recycled – user772401 Aug 27 '15 at 05:32
  • 1
    if use leaves you can delete it from your session, and when new one joins even with the same number there will be unpopulated key in your sessions table – Anton Krug Jan 04 '17 at 00:59
  • 1
    probably there might be problem that on disconect the _handle will be gone and you can't remove it from the table, probably you could save the handle inside the ws? – Anton Krug Jan 04 '17 at 14:35
3

You can check the connection object. It has built-in identification for every connected client; you can find it here:

let id=ws._ultron.id;
console.log(id);
RickL
  • 3,318
  • 10
  • 38
  • 39
rrkjonnapalli
  • 357
  • 1
  • 3
  • 7
2

One possible solution here could be appending the deviceId in front of the user id, so we get to separate multiple users with same user id but on different devices.

ws://xxxxxxx:9000/userID/<<deviceId>>

cyberrspiritt
  • 908
  • 8
  • 26
1

Use a global counter variable and assign its value for every new connection:

const wss = new WebSocket.Server({server});
let count_clients = 0;
wss.on('connection', function connection(ws){
    ws.id=count_clients++;
    console.log(`new connection, ws.id=${ws.id}, ${ws._socket.remoteAddress}:${ws._socket.remotePort} #clients=${wss.clients.size}`);
    ws.on('close', req => {console.log(`disconnected, ws.id=${ws.id}, ${ws._socket.remoteAddress}:${ws._socket.remotePort} #clients=${wss.clients.size}`);});
...
Mendi Barel
  • 3,350
  • 1
  • 23
  • 24
0

By clients if you mean the open connections, then you can use ws.upgradeReq.headers['sec-websocket-key'] as the identifier. And keep all socket objects in an array.

But if you want to identify your user then you'll need to add user specific data to socket object.

Deepak Chaudhary
  • 476
  • 5
  • 15
0

If someone here is maybe using koa-websocket library, server instance of WebSocket is attached to ctx along side the request. That makes it really easy to manipulate the wss.clients Set (set of sessions in ws). For example pass parameters through URL and add it to Websocket instance something like this:

const wss = ctx.app.ws.server
const { userId } = ctx.request.query

try{

   ctx.websocket.uid = userId

}catch(err){
    console.log(err)
}
Marko Balažic
  • 546
  • 5
  • 8
0

Here is what I did:

* on connect, server generate an unique id (e.g uuid) for the connection,
    * save it in memory, (e.g as key of map),
    * send back to client in response,
    * 
* 
* client save the id, on each request will also send the id as part of request data,
* then server identify the client by id, on receive further request,
* 
* server maintain client, e.g cleanup on close/error,
* 

I've impl the idea, it works well to identify the client.
And, I also achieved group/topic broadcast based on the idea, which need the server to maintain extra info.

Eric
  • 22,183
  • 20
  • 145
  • 196
0

There are a lot of interesting answers that do the job, however they mostly seem unclean, that is if you don't mind mutating the ws object. I did it this way because I'm using TypeScript and you can't arbitrarily add properties to objects.

import WebSocket from 'ws'

declare module 'ws' {
  interface WebSocket {
    id: any
    key: string
  }
}

The id doesn't have to be type any can be number or string depending on how you ID your connections. I haven't flushed out the system yet but for now when a connection is made, I just assign a random number.

const socketConnection = (socket: WebSocket.WebSocket): void => {
  socket.id = Math.random()
  console.log(socket.id)
  const msg = JSON.stringify({ res: `[open] Welcome to the WebSocket server!` })
  socket.send(msg)
}

This can be modified at any point so once I authenticate the connection I plan on assigning a relative ID here and might even add in a key property if I want to do some more fancy stuff.

How this works is explained in the Module Augmentation section of the documentation.

TypeScript: Module Augmentation

You can check that it's still assigned by looking over multiple messages in the onmessage event.

const socketMessage = (socket: WebSocket.WebSocket): void => {
  socket.on('message', async (message: WebSocket.RawData) => {
    console.log(socket.id)
    console.log(socket.key)
  })
}

Oh and a note, I made this module declaration in the document where I setup my socket. But the modification does populate across documents. For example in the AuthController I started prototyping I use it this way.

export default class AuthController {
  public static connections: DLinkedList = new DLinkedList()

  static async validate(request: { id: string, socket: WebSocket.WebSocket }): Promise<void> {
    console.log('test', request.socket.id)

    this.connections.add(request.socket, request.id)
    request.socket.send(JSON.stringify({ res: true }))
    console.log(this.connections.size())
  }

  static getSocket(id: string): WebSocket.WebSocket {
    return this.connections.getAtKey(id).data
  }

  static removeSocket(socket: WebSocket.WebSocket) {

  }
}

You can also do this in pure JS just by directly modifying the WebSocket object prototype. Some of the answers here talk about it. I haven't done it myself but the principle is similar.

Add a method to an existing class in typescript?

Hope this is useful.

Andrew
  • 399
  • 6
  • 15