102

I wrote a code for broadcasting a message to all users:

// websocket and http servers
var webSocketServer = require('websocket').server;

...
...
var clients = [ ];

var server = http.createServer(function(request, response) {
    // Not important for us. We're writing WebSocket server, not HTTP server
});
server.listen(webSocketsServerPort, function() {
  ...
});

var wsServer = new webSocketServer({
    // WebSocket server is tied to a HTTP server. 
    httpServer: server
});

// This callback function is called every time someone
// tries to connect to the WebSocket server
wsServer.on('request', function(request) {
...
var connection = request.accept(null, request.origin); 
var index = clients.push(connection) - 1;
...

Please notice:

  • I don't have any user reference but only a connection .
  • All users connection are stored in an array.

Goal: Let's say that the Node.js server wants to send a message to a specific client (John). How would the NodeJs server know which connection John has? The Node.js server doesn't even know John. all it sees is the connections.

So, I believe that now, I shouldn't store users only by their connection, instead, I need to store an object, which will contain the userId and the connection object.

Idea:

  • When the page finishes loading (DOM ready) - establish a connection to the Node.js server.

  • When the Node.js server accept a connection - generate a unique string and send it to the client browser. Store the user connection and the unique string in an object. e.g. {UserID:"6", value: {connectionObject}}

  • At client side, when this message arrives - store it in a hidden field or cookie. (for future requests to the NodeJs server )


When the server wants to send a message to John:

  • Find john's UserID in the dictionary and send a message by the corresponding connection.

  • please notice there is no asp.net server code invloced here (in the message mechanism). only NodeJs .*

Question:

Is this the right way to go?

Community
  • 1
  • 1
Royi Namir
  • 144,742
  • 138
  • 468
  • 792

5 Answers5

98

This is not only the right way to go, but the only way. Basically each connection needs a unique ID. Otherwise you won't be able to identify them, it's as simple as that.

Now how you will represent it it's a different thing. Making an object with id and connection properties is a good way to do that ( I would definitely go for it ). You could also attach the id directly to connection object.

Also remember that if you want communication between users, then you have to send target user's ID as well, i.e. when user A wants to send a message to user B, then obviously A has to know the ID of B.

freakish
  • 54,167
  • 9
  • 132
  • 169
  • 1
    @RoyiNamir Again, you don't have much choice. Browser will kill the connection, so you have tohandle disconnection and reconnection. Also you probably will want to implement some kind of timeout mechanism (destroy the wrapper object on the server side only after timeout) to avoid unnecessary notifications when this happens (assuming you want to notify other users about disconnection). – freakish May 01 '13 at 14:42
  • 1
    @RoyiNamir As for two unique names: the other idea is to keep the object `SOCKETS = {};` on the server side and add a socket to a list for a give ID, i.e. `SOCKETS["John"] = [];` and `SOCKETS["John"].push(conn);`. You will probably need that since a user can open multiple tabs. – freakish May 01 '13 at 14:45
  • @RoyiNamir I don't know what library you are using, but normally there should be a handler for that. Note that this will only work, when client terminates the connection properly (like closing the tab/browser for example). If something wrong happens (power plant explodes ;)), then you won't now about that. Therefore you will need a pinging mechanism anyway. – freakish May 01 '13 at 14:46
  • https://github.com/Worlize/WebSocket-Node ( and I know I should move to Socket.io) :-) – Royi Namir May 01 '13 at 14:47
  • @RoyiNamir Yep, I think that will do: `connection.on('close', callback);`. As for Socket.IO: that's totally your opinion. :) I don't like it at all, since it doesn't scale well. – freakish May 01 '13 at 14:48
  • Really ? Thanks for that info. ( about scaling). I will think about your solution more deeply. thanks :-) – Royi Namir May 01 '13 at 14:51
  • @RoyiNamir No worries, let me know if you have more questions. :) – freakish May 01 '13 at 14:51
  • @RoyiNamir Guys I read your explanations and am following http://docs.oracle.com/javaee/7/tutorial/doc/websocket011.htm#BABGCEHE but not sure how to attach users ids to connection object should I attach them at theend of url ? ws://localhost:8080/dukeetf2/dukeetf?id=2123 ? – J888 Nov 19 '13 at 04:41
  • @freakish Guys I read your explanations and am following http://docs.oracle.com/javaee/7/tutorial/doc/websocket011.htm#BABGCEHE but not sure how to attach users ids to connection object should I attach them at theend of url ? ws://localhost:8080/dukeetf2/dukeetf?id=2123 ? – J888 Nov 19 '13 at 04:42
  • 8
    @freakish : Careful, this is only a good solution for a one process app. If you scale your application using multiple processes (or servers), they will not share the same memory - so the iteration using locally stored objects will not work. It's better to iterate all the local connections directly, storing the UUID on the connection itself. Also, for scaling I would recommend Redis. – Myst Jul 28 '15 at 14:51
  • From my experience I store all user connection id to Redis then use pub feature of Redis to publish send message event to all process then let each process handle it. It work well!! – Phatthana Batt May 27 '16 at 09:00
  • 1
    Can you please tell me how to send message to a particular client using connection object? I have saved the user id in connection object as stated above. I just want to know how to use connection object while sending message from server to particular client(i have id and connection object of that user). Thanks. – Vardan Jul 14 '16 at 06:50
  • @Myst, can you please explain how exactly,able to to scale ? Also I store the id in connection object itself , is all the connection object will shared across the multiple servers if am using a load-balancer.? – Asish AP Oct 31 '17 at 17:22
  • 2
    @AsishAP , this is a long discussion... I recommend that you start with reading [this article](https://hackernoon.com/scaling-websockets-9a31497af051) just to get oriented and than maybe search SO for other questions, [here's one worth reading](https://stackoverflow.com/questions/4710420/scaling-node-js). – Myst Nov 01 '17 at 00:31
  • what about this comment: https://stackoverflow.com/questions/13364243/websocketserver-node-js-how-to-differentiate-clients#comment55441648_17223609 saying that your solution is not thread-safe? – T.Todua Nov 07 '22 at 13:24
  • @T.Todua what do you mean? I don't see anyone mentioning threads in the link. Besides, JavaScript is single threaded, it doesn't have thread safety issues. – freakish Nov 07 '22 at 14:40
  • @freakish it's not mentioning the thread or answer per se, I mean, that person commented below another answer, which was like your answer, and says that, storing the ids in variable is not safe and causes unexpected results. please read my referred comment to understand the problem and mention two words - if you can of course as a courtesy. – T.Todua Nov 07 '22 at 19:51
  • 1
    @T.Todua there's absolutely nothing unsafe about storing ids in variable. Plus I'm not sure what an alternative is. I mean, variables store data, that's what they are for. Even if you write scalable, multiserver solution, even if you use Redis or something else, each server still has to keep track of its own connections. There's no way to avoid this. – freakish Nov 07 '22 at 21:28
56

Here's a simple chat server private/direct messaging.

package.json

{
  "name": "chat-server",
  "version": "0.0.1",
  "description": "WebSocket chat server",
  "dependencies": {
    "ws": "0.4.x"
  }
}

server.js

var webSocketServer = new (require('ws')).Server({port: (process.env.PORT || 5000)}),
    webSockets = {} // userID: webSocket

// CONNECT /:userID
// wscat -c ws://localhost:5000/1
webSocketServer.on('connection', function (webSocket) {
  var userID = parseInt(webSocket.upgradeReq.url.substr(1), 10)
  webSockets[userID] = webSocket
  console.log('connected: ' + userID + ' in ' + Object.getOwnPropertyNames(webSockets))

  // Forward Message
  //
  // Receive               Example
  // [toUserID, text]      [2, "Hello, World!"]
  //
  // Send                  Example
  // [fromUserID, text]    [1, "Hello, World!"]
  webSocket.on('message', function(message) {
    console.log('received from ' + userID + ': ' + message)
    var messageArray = JSON.parse(message)
    var toUserWebSocket = webSockets[messageArray[0]]
    if (toUserWebSocket) {
      console.log('sent to ' + messageArray[0] + ': ' + JSON.stringify(messageArray))
      messageArray[0] = userID
      toUserWebSocket.send(JSON.stringify(messageArray))
    }
  })

  webSocket.on('close', function () {
    delete webSockets[userID]
    console.log('deleted: ' + userID)
  })
})

Instructions

To test it out, run npm install to install ws. Then, to start the chat server, run node server.js (or npm start) in one Terminal tab. Then, in another Terminal tab, run wscat -c ws://localhost:5000/1, where 1 is the connecting user's user ID. Then, in a third Terminal tab, run wscat -c ws://localhost:5000/2, and then, to send a message from user 2 to 1, enter ["1", "Hello, World!"].

Shortcomings

This chat server is very simple.

  • Persistence

    It doesn't store messages to a database, such as PostgreSQL. So, the user you're sending a message to must be connected to the server to receive it. Otherwise, the message is lost.

  • Security

    It is insecure.

    • If I know the server's URL and Alice's user ID, then I can impersonate Alice, ie, connect to the server as her, allowing me to receive her new incoming messages and send messages from her to any user whose user ID I also know. To make it more secure, modify the server to accept your access token (instead of your user ID) when connecting. Then, the server can get your user ID from your access token and authenticate you.

    • I'm not sure if it supports a WebSocket Secure (wss://) connection since I've only tested it on localhost, and I'm not sure how to connect securely from localhost.

ma11hew28
  • 121,420
  • 116
  • 450
  • 651
  • Does anyone know how to make a [WebSocket Secure localhost connection](http://stackoverflow.com/questions/19469445/websocket-secure-localhost-connection)? – ma11hew28 Oct 19 '13 at 18:30
  • 1
    Same here. But if you replace the 'upgradeReq...' part with 'ws.upgradeReq.headers['sec-websocket-key']' it works. (via https://github.com/websockets/ws/issues/310 ) – dirkk0 Feb 14 '15 at 11:59
  • 1
    Best example to kick start the WS in practice ! Thanks – Tom Raganowicz Apr 05 '18 at 10:59
  • 1
    Great example! I've been looking for an answer like this for the past few days – DIRTY DAVE Sep 29 '19 at 00:06
  • How to send messages outside ws.on("connection") ?? – Iglesias Leonardo Mar 02 '23 at 16:21
  • I am pretty sure you cannot just send back a websocket response to one client. You have to send it back to anyone who has a connection open to a specific WS URL. You can use IDs to choose who you want to display the data to, inside the client GUI? – Charles Robertson Aug 05 '23 at 18:19
15

For people using ws version 3 or above. If you want to use the answer provided by @ma11hew28, simply change this block as following.

webSocketServer.on('connection', function (webSocket) {
  var userID = parseInt(webSocket.upgradeReq.url.substr(1), 10)
webSocketServer.on('connection', function (webSocket, req) {
  var userID = parseInt(req.url.substr(1), 10)

ws package has moved upgradeReq to request object and you can check the following link for further detail.

Reference: https://github.com/websockets/ws/issues/1114

Dylandy
  • 171
  • 1
  • 5
1

I would like to share what I have done. Hope it doesn't waste your time.

I created database table holding field ID, IP, username, logintime and logouttime. When a user logs in logintime will be currect unixtimestamp unix. And when connection is started in websocket database checks for largest logintime. It will be come user logged in.

And for when user logs out it will store currect logouttime. The user will become who left the app.

Whenever there is new message, Websocket ID and IP are compared and related username will be displayed. Following are sample code...

// when a client connects
function wsOnOpen($clientID) {
      global $Server;
      $ip = long2ip( $Server->wsClients[$clientID][6] );

      require_once('config.php');
      require_once CLASSES . 'class.db.php';
      require_once CLASSES . 'class.log.php';

      $db = new database();
      $loga = new log($db);

      //Getting the last login person time and username
      $conditions = "WHERE which = 'login' ORDER BY id DESC LIMIT 0, 1";
      $logs = $loga->get_logs($conditions);
      foreach($logs as $rows) {

              $destination = $rows["user"];
              $idh = md5("$rows[user]".md5($rows["time"]));

              if ( $clientID > $rows["what"]) {
                      $conditions = "ip = '$ip', clientID = '$clientID'  

                      WHERE logintime = '$rows[time]'";
                      $loga->update_log($conditions);
             }
      }
      ...//rest of the things
} 
tashi
  • 249
  • 1
  • 3
  • 16
0

interesting post (similar to what I am doing). We are making an API (in C#) to connect dispensers with WebSockets, for each dispenser we create a ConcurrentDictionary that stores the WebSocket and the DispenserId making it easy for each Dispenser to create a WebSocket and use it afterwards without thread problems (invoking specific functions on the WebSocket like GetSettings or RequestTicket). The difference for you example is the use of ConcurrentDictionary instead of an array to isolate each element (never attempted to do such in javascript). Best regards,

wolf354
  • 147
  • 1
  • 6