websockets / ws

Simple to use, blazing fast and thoroughly tested WebSocket client and server for Node.js
MIT License
21.75k stars 2.42k forks source link

Incorrect Broadcast Distribution as Expected #2158

Closed mahiraltinkaya closed 1 year ago

mahiraltinkaya commented 1 year ago

Is there an existing issue for this?

Description

``I am not entirely sure where the problem originates from. I am new to using the ws library and I have spent a considerable amount of time trying to address this issue. The problem unfolds as follows: I am logging in from two mobile applications at different times and I intend to broadcast the updated list of online users to all users. However, while the latest connection receives the complete data, previous connections do not receive any data. Although the distribution seems successful, it implies that the transmission is not taking place. Apart from this issue, all other actions are successful. Additionally, when an application is closed, the connection does not immediately drop, and the user still appears online. I would like to resolve these two core issues and continue using this package, particularly due to its optimization for data sizes.

SERVER -- -- -- -- -


var server = http.createServer(app);
const wss = new WebSocketServer({ server, perMessageDeflate: false });

function heartbeat() {
  this.isAlive = true;
}

function broadcastClientList(list) {
  wss.clients.forEach((client) => {
    if (list.includes(client.id) && client.readyState === WebSocket.OPEN) {
      const date = new Date().getTime();
      console.log(list, 'LIST', 'ID', client.id, 'DATE', date);
      client.send(
        JSON.stringify({
          t: 'clientList',
          l: list,
          reciever: client.id,
          date: date,
        })
      );
    }
  });
}
wss.on('connection', function connection(ws, req) {
  ws.isAlive = true;
  const clientId = req.url?.replace('/?socketId=', '');
  ws.id = clientId;
  SOCKET_LIST[clientId] = ws;
  clientList.push(clientId);
  clientList = [...new Set(clientList)];
  broadcastClientList(clientList);

  ws.on('message', function message(data) {
    const x = JSON.parse(data);

    if (x.t === 'connect') {
      console.log('CONNECTED CLIENT', x.id);
    }

    if (x.t === 'emoji') {
      gameController.sendEmote(x, ws, wss.clients);
    }

    if (x.t === 'position') {
      gameController.sendPosition(x, ws, wss.clients);
    }

    if (x.t === 'searchGame') {
      gameController.searchGame(x, ws, wss.clients);
    }

    if (x.t === 'leftLobby') {
      gameController.leftLobby(x);
    }

    if (x.t === 'csg') {
      gameController.createSpecialGame(x, ws, wss.clients);
    }

    if (x.t === 'iamReady') {
      gameController.iamReady(x, ws, wss.clients);
    }
  });
  ws.on('close', () => {
    ws.isAlive = false;
    notifications.destroy({
      where: {
        [Op.or]: [{ p1: ws.id }, { p2: ws.id }],
        status: 0,
      },
    });
    console.log('DESTROYED SOCKET', ws.id);
    clientList = clientList.filter((item) => item !== ws.id);
    gameController.leftSocket(ws.id, clientList);
    broadcastClientList(clientList);
    console.log('WSS SIZE', wss.clients.size, 'CLIENT LIST SIZE', clientList);
    ws.close();
  });
  ws.on('pong', heartbeat);
  ws.on('error', console.error);
});

const interval = setInterval(function ping() {
  wss.clients.forEach(function each(ws) {
    if (ws.readyState !== WebSocket.OPEN && ws.isAlive === false)
      return ws.terminate();

    ws.isAlive = false;
    ws.ping();
  });
}, 10000);

wss.on('close', function close() {
  clearInterval(interval);
});

CLIENT --------


  React.useEffect(() => {
    const uid = new ShortUniqueId({length: 12});
    const genSocket = uid();
    if (!socketId) dispatch(setSocketId(genSocket));

    const ws = new WebSocket(
      `${BASE_URL_SOCKET}?socketId=${socketId || genSocket}`,
    );

    ws.onopen = () => {
      dispatch(setWebSocket(ws));
      ws.send(JSON.stringify({t: 'connect', id: socketId || genSocket}));
    };

    ws.onmessage = (e: any) => {
      const data = JSON.parse(e.data);
      if (data.t === 'clientList') {
        console.log(
          'CONNECT LİST',
          data.l,
          'RECEIVER',
          data.reciever,
          'DATE',
          data.date,
        );
        dispatch(setOnlinePlayers(data.l));
      }
    };

    ws.onerror = e => {
      console.log('WebSocket error:', e);
      if (e.message) console.log('ERRR MESSAGE', e.message);
    };

    ws.onclose = e => {
      if (e.code) {
        dispatch(setWebSocket(null));
        const setTimeoutId = setTimeout(() => {
          clearTimeout(setTimeoutId);
        }, 5000);
      }
    };

    return () => {};
  }, []);```

### ws version

8.13.0

### Node.js Version

18.17

### System

$ npx envinfo --system
Need to install the following packages:
envinfo@7.10.0
Ok to proceed? (y) y

  System:
    OS: Windows 10 10.0.22621
    CPU: (20) x64 12th Gen Intel(R) Core(TM) i7-12700H
    Memory: 16.51 GB / 31.75 GB

### Expected result

I expect the list that I believe is being sent from the server to be seen in the same way on the React Native side.Two users cannot be aware of the new user being online

### Actual result

Node js Logs
WSS SIZE 0 CLIENT LIST SIZE []
[ 'EikAq9XylHGl' ] LIST ID EikAq9XylHGl DATE **1691846854130**
GET /api/v1/auth/getVersion 304 1.642 ms - -
POST /api/v1/auth/login 200 2.525 ms - 760
POST /api/v1/auth/login 200 2.303 ms - 760
CONNECTED CLIENT EikAq9XylHGl
[ 'EikAq9XylHGl', 'rhwwGyOUBrUS' ] LIST ID EikAq9XylHGl DATE **1691846911237**
[ 'EikAq9XylHGl', 'rhwwGyOUBrUS' ] LIST ID rhwwGyOUBrUS DATE **1691846911238**

React Native Logs
 LOG  Running "findme" with {"rootTag":11}
 LOG  CONNECT LİST ["EikAq9XylHGl"] RECEIVER EikAq9XylHGl DATE **1691846854130**
 BUNDLE  ./index.js

 LOG  Running "findme" with {"rootTag":11}
 LOG  CONNECT LİST ["EikAq9XylHGl", "rhwwGyOUBrUS"] RECEIVER rhwwGyOUBrUS DATE **1691846911238**

### Attachments

_No response_
lpinca commented 1 year ago

Please write a minimal test case (using only ws) that I can run to reproduce the issue. As it is, the issue is not actionable.

lpinca commented 1 year ago

Here is an example that tries to reproduce what is written in the issue description. Everything seems to work correctly.

import { WebSocketServer, WebSocket } from 'ws';

const websocketServer = new WebSocketServer({ port: 0 }, function () {
  const { port } = websocketServer.address();

  const websocket1 = new WebSocket(`ws://localhost:${port}/?id=1`);

  websocket1.on('message', function (data) {
    console.log('websocket 1 received: %s', data.toString());
  });

  setTimeout(function () {
    const websocket2 = new WebSocket(`ws://localhost:${port}/?id=2`);

    websocket2.on('message', function (data) {
      console.log('websocket 2 received: %s', data.toString());
    });
  }, 2000);

  setTimeout(function () {
    const websocket3 = new WebSocket(`ws://localhost:${port}/?id=3`);

    websocket3.on('message', function (data) {
      console.log('websocket 3 received: %s', data.toString());
    });
  }, 4000);
});

const ids = [];

websocketServer.on('connection', function (ws, request) {
  const id = new URL(request.url, 'ws://localhost/').searchParams.get('id');

  console.log('websocket %s connected', id);

  ids.push(id);

  broadcast();

  ws.on('close', function () {
    ids.splice(ids.indexOf(id), 1);

    broadcast();
  });

  setTimeout(function () {
    console.log('websocket %s closing', id);

    ws.close();
  }, 6000);
});

function broadcast() {
  for (const client of websocketServer.clients) {
    client.send(JSON.stringify(ids));
  }
}
mahiraltinkaya commented 1 year ago

Hello @lpinca

Thank you for your response. I have identified that the primary cause of the issue here is related to React Native's development mode. I will implement your suggestion as a best practice. Thank you.

I am working on a 4-core VPS server and creating clusters using pm2. I am maintaining a list of users in an array. Different clusters correspond to different arrays, eliminating the possibility of two online users encountering each other. How can I enable the clusters to access the same array of socket users?

lpinca commented 1 year ago

How can I enable the clusters to access the same array of socket users?

Use redis or something like that to maintain a shared state.

mahiraltinkaya commented 1 year ago

Hey @lpinca Thank you for suggestion. From what you're saying, I understand this: Even if there are more than 1 clusters, a single socket instance is created, and it can be accessed from each cluster?

lpinca commented 1 year ago

You can only access a socket from its own process/server but you can use a communication system between multiple processes/servers.

mahiraltinkaya commented 1 year ago

Hey @lpinca ,

Can I ask one last question?

After a successful connection is established between the server and the client, I am attempting to reconnect in case the connection is lost. When the connection is reestablished, data sent by the client is received and processed by the server, and then it is intended to be distributed to other users. However, after successfully receiving the data, the server encounters an issue while distributing the processed data to other clients. Although the data appears to be sent, the client does not receive this data. What kind of problem could we be facing here? These functions work correctly if the connection is initially established without any issues and there are no disconnects, but they stop working when re-establishing client connections.

I have a unique ID for each user, and I'm attempting to assign this unique ID to each connection, trying to send requests to the client device where the user has established the connection.

lpinca commented 1 year ago

Although the data appears to be sent, the client does not receive this data. What kind of problem could we be facing here?

I don't know. Maybe you are sending the data to the old closed websocket. Make sure that the data is sent to the new websocket.

lpinca commented 1 year ago

I'm closing this as answered.