socketio / socket.io

Realtime application framework (Node.JS server)
https://socket.io
MIT License
60.88k stars 10.09k forks source link

Emit to room not work with uWebsocketjs #5139

Open SaltFish001 opened 1 month ago

SaltFish001 commented 1 month ago

Describe the bug Emit some message to room not work with uWebsocketjs, but clients does`t got any message. To Reproduce

Please fill the following code example:

Socket.IO server version: x.y.z

Server

import { Server as socket_server } from 'socket.io';
import { App } from 'uWebSockets.js';

const uapp = App();
const socket_instance = new socket_server({
  path: '/socket',
});
socket_instance.attachApp(uapp);
socket_instance.use((scoket, next) => {
  console.log('socket connect >>>', scoket.id);
  scoket.join('test');
  setTimeout(() => {
    console.log('socket emit >>>', scoket.rooms);
    socket_instance.to('test').emit('message', 'hello world');
  }, 2000);
  return next();
});

uapp.listen(9999, (token) => {
  console.log(token);
  console.log('server is listening on port 8888');
});

Socket.IO client version: x.y.z

Client

import { io } from 'socket.io-client';
const client = io('http://localhost:9999', {
  path: '/socket',
  transports: ['websocket'],
});

client.onAny((...arg) => {
  console.log('onany >>>', arg);
});
client.connect();

Expected behavior A clear and concise description of what you expected to happen.

Platform:

Additional context Socket.io & Socket.io-client 4.7.5 uWebsocketjs 20.44.0

darrachequesne commented 1 month ago

Hi! I was indeed able to reproduce the issue.

It does not seem to happen with uWebSockets.js#v20.30.0 though, this needs some additional investigation.

SaltFish001 commented 1 month ago

Hi! I was indeed able to reproduce the issue.嗨!我确实能够重现这个问题。

It does not seem to happen with uWebSockets.js#v20.30.0 though, this needs some additional investigation.然而,这种情况在 身上似乎并未出现,这需要进一步的调查。

Thanks reply. Right now I can use fetchSockets get all socket , then foreach sockets to emit event. Hope this is not a hard work for you.

TXWSLYF commented 1 month ago

I think I found the cause of this bug.This is because during the execution of the middleware, the socket has not yet been added to the adapter's namespace.

const { Server: socket_server } = require("socket.io");
const { App } = require("uWebSockets.js");

const uapp = App();
const socket_instance = new socket_server({
  path: "/socket",
});
socket_instance.attachApp(uapp);
socket_instance.use((scoket, next) => {
  console.log("socket connect >>>", scoket.id);

  // console.log adapter's namespace
  console.log(scoket.adapter.nsp.sockets, '----');
  scoket.join("test");

  setTimeout(() => {
    console.log("socket emit >>>", scoket.rooms);
    socket_instance.to("test").emit("message", "hello world");
  }, 2000);
  return next();
});

uapp.listen(9999, (token) => {
  console.log(token);
  console.log("server is listening on port 9999");
});
image

when call the scoket.join("test"), it's actually call: https://github.com/socketio/socket.io/blob/582655f679ccc43f0a9cbef1f13ea3cde07dc2e1/packages/socket.io/lib/socket.ts#L554-L561

when the socket_instance.attachApp is called, the Adapter.prototype.addAll would be rewrite: https://github.com/socketio/socket.io/blob/582655f679ccc43f0a9cbef1f13ea3cde07dc2e1/packages/socket.io/lib/uws.ts#L14-L33

this bug happend here, we can't find the socket in the nsp now, so it's just return and didn't call the subscribe fun.

const socket: Socket = this.nsp.sockets.get(id); 
 if (!socket) { 
   return; 
 } 

and why we can't get the socket by id from the nsp here is due to the execution order of the middleware and _doConnect method. https://github.com/socketio/socket.io/blob/582655f679ccc43f0a9cbef1f13ea3cde07dc2e1/packages/socket.io/lib/namespace.ts#L334-L357

The _doConnect method will only be executed when all the middleware has been executed, which track the socket.

TXWSLYF commented 1 month ago

for the example below, we don't use uWebsocketjs this time, even if the socket has not yet been added to the adapter's namespace, it also works well.

const { Server: socket_server } = require("socket.io");

const socket_instance = new socket_server({
  path: "/socket",
});
socket_instance.use((scoket, next) => {
  console.log("socket connect >>>", scoket.id);
  console.log(scoket.adapter.nsp.sockets, '----');
  scoket.join("test");

  setTimeout(() => {
    console.log("socket emit >>>", scoket.rooms);
    socket_instance.to("test").emit("message", "hello world");
  }, 2000);
  return next();
});

socket_instance.listen(9999, (token) => {
  console.log(token);
  console.log("server is listening on port 9999");
});
image

this is because by default, the join method use the in-memory-adapter,it don't check if the socket exists now but just store the relation. https://github.com/socketio/socket.io/blob/582655f679ccc43f0a9cbef1f13ea3cde07dc2e1/packages/socket.io-adapter/lib/in-memory-adapter.ts#L87-L104

and get the socket when emit message. https://github.com/socketio/socket.io/blob/582655f679ccc43f0a9cbef1f13ea3cde07dc2e1/packages/socket.io-adapter/lib/in-memory-adapter.ts#L333-L358

TXWSLYF commented 1 month ago

I created a fix but don't know if it will have any other impacts, would appreciate if you could check it.