2

I have a javascript GameClient that uses SocketIO to send messages to a nodeJs server. Multiple users can open the GameClient separately and send messages to the server.

GameClient
GameClient  ---> NodeJS Server
GameClient

The server can send messages to specific clients using io.to(socketid).emit(). The code looks something like this:

CLIENT

this.socket = io({ timeout: 60000 })
this.socket.on('connect', () => Settings.getInstance().socketid = this.socket.id)
this.socket.on('reconnect', (attemptNumber:number) => console.log("reconnecting..."))

const json = JSON.Stringify({socketid:this.socket.id, name:"Old Billy Bob"})
this.socket.emit('user created', json)

SERVER (simplified for clarity, just keeping track of one user here)

user = {}

io.on('connection', (socket) => {
    console.log('new connection')
    socket.on('disconnect', () => {
        console.log('user disconnected')
    });

    socket.on('user created', (json) => {
        user = JSON.parse(json)
    });
});

// demo code, send a message to our user
io.to(user.socketid).emit("message to one user")

PROBLEM

When the client browser tab becomes inactive for any reason at all, the client disconnects and reconnects and gets a new socket connection ID. This actually happens a lot in Chrome and Safari.

The server only knows the old connection id, so now it can't send direct messages any more. How do I keep the socket connection id synchronised on the client and server?

Since the server also gets a reconnected event, how does it know which user reconnected?

Kokodoko
  • 26,167
  • 33
  • 120
  • 197

1 Answers1

2

The answer to your question is quite simple: you need a way to identify who is who. And that is not socket.id because this only identifies sockets, not users, as you've already noticed.

So you need some authentication mechanism. Once a user authenticates you can reuse his true id (whether it is simply a name or an integer in a database is irrelevant). And then on the server side you keep a collection of pairs (true_id, socket_id). And whenever a message comes to that user, you broadcast it to all matched socket.io objects.

Edit: So here's the flow:

  1. Client authenticates with the server, the server sends him his own true_id, which the client stores somewhere. The client may also store some session_id or maybe some other mechanism that will allow him fast reauthentication in case of disconnection (note: do not store credentials, its a security issue).
  2. The server keeps track of (true_id, socket_id) pairs in the form of a double way, mutlivalue map (it's an implementation detail what kind of data structure should be used here, maybe two {} objects is enough). If a connection dies then (true_id, socket_id) entry is removed. Note that for a given true_id there still may be some other socket_id alive. And so it doesn't mean that the user disconnected. It only means that this particular channel is dead.
  3. Users don't care about socket_id, they only care about true_id. What you emit is {target_id: true_id, ...} instead of {target_id: socket_id, ...} on the client side, when you want to send a direct message.
  4. When the server receives such message with true_id inside, it retrieves all (true_id, socket_id) pairs and passes the message to all of these sockets (note: maybe you don't even need socket_id, you can simply store socket objects here). Although this is a business logic: do you allow multiple connections per user? I would. There are many edge cases here (like for example a client thinks that he disconnected, but the server thinks he is still connected, etc) and making this 100% correct is unfortunately impossible (due to the nature of networking). But with a bit of effort it is possible to make it work 99% of the time.
  5. If a connection dies then it is your client's responsibility to automatically reconnect and reauthenticate. New socket_id for old true_id is generated on the server side.

Let me emphasize this again: clients don't care about socket_id at all. Because that doesn't identify them. This only identifies a channel. And only the server cares about this information.

freakish
  • 54,167
  • 9
  • 132
  • 169
  • Well my users also have unique id's, but I still don't understand how, after a reconnection event, I can update the socket id on both the server and client side at the same time. I still need the socket id to send a message using `io.to(socketId).emit('hello there');` – Kokodoko Oct 02 '19 at 20:23
  • Thanks for the explanation! So, when a client disconnects and then reconnects, it has to manually send another message back to the server so that the server can couple the client's id with the new socket id again? And the server has to maintain a list of all users+socketids? – Kokodoko Oct 02 '19 at 21:09
  • @Kokodoko Indeed. This "manual message sending" is exactly the authentication step. This step is also necessary due to security (it greatly reduces the risk of impersonation). And yes, the server has to maintain a list/map. – freakish Oct 02 '19 at 21:16