socket.io+redis+expressjs集群-在expressjs请求中获取socket对象

socket.io+redis+expressjs cluster - get socket object in expressjs request

基于此答案的问题:

我试图找到这个解决方案,但似乎没有我需要的方法。

集群 expressjssocket.io 我们可以使用 Redis 共享会话并在 io 世界(io.sockets.on('connection',...)中发送 io 消息。问题是如果我们想在 expressjs 世界 (route.get/post).

中发送消息(或使用简单的 socket.join/leave

如果我们不使用集群,我们可以将客户端 socket 对象附加到 express request 对象(或简单地 export io 对象),然后使用随时在任何 GET/POST 路线上。

另一方面,如果我们正在聚类并使用上述方法在 expressjs 世界中获取 socket 对象,有时 socket 对象是未定义的,因为 socket 此客户端的对象在其他 worker.

处初始化

一些示例流程:

在这种情况下,当客户端执行 POST 时,只有 worker 2 知道此客户端的 socket 对象。所以这将得到一个未定义的 socket 对象。

所以,问题:

我们如何从任何 worker 获取客户端 socket 对象以在 expressjs request 对象上重用它。

也许我的代码有误,但与上述答案的 link 差不多。


备注

使用最后的nodejs,socket.io,expressjs,socket.io-redis,redis...版本

不要犹豫,问问题!


更新 1

可能的解决方案,但仍需测试。不知道这是否是一个很好的解决方案。

更新 2

与更新 1 类似,但使用 https://nodejs.org/dist/latest-v5.x/docs/api/cluster.html#cluster_event_message

好吧,终于尝试了代码并且它可以工作(有一些拼写错误的修改和其他东西)但我确信在某个地方需要更好的代码。所以我愿意接受更多答案!

此代码是我的 socket.io 模块的一部分,用于授权客户端套接字和其他一些内容...

  var redis = require("redis");
  var redisPub = redis.createClient();
  var redisSub = redis.createClient();
  var PubSubChannel = "clusterChannel";

  // Function that checks if this worker knows the socket object of this socketId.
  // If not, publish the message to all the other sockets (workers)
  io.socketDo = function (type, socketId, roomName) {
    if (typeof io.sockets.connected[socketId] != "undefined") {
      if (type === "join") {
        return io.sockets.connected[socketId].join(roomName);
      }
      if (type === "leave") {
        return io.sockets.connected[socketId].leave(roomName);
      }
    } else {
      redisPub.publish(
        PubSubChannel,
        JSON.stringify({
          type: type,
          socketId: '' + socketId,
          roomName: roomName
        })
      );
    }
  };

  // Subscribe to some channel
  redisSub.subscribe(PubSubChannel);

  // When this worker receive a message from channel "PubSubChannel" checks
  // if it have the socket object for this socketId and do the operation
  redisSub.on("message", function (channel, data) {
    data = JSON.parse(data);
    var type = data.type;
    var socketId = data.socketId;
    var roomName = data.roomName;
    if ((type === "join" || type === "leave") && channel == PubSubChannel){
      if (typeof io.sockets.connected[socketId] != "undefined") {
        if (type === "join") {
          return io.sockets.connected[socketId].join(roomName);
        }
        if (type === "leave") {
          return io.sockets.connected[socketId].leave(roomName);
        }
      }
    }
  });

然后只需导出模块并将其附加到您的 expressjs request => req.io = io

// req.session.socketId value is fetched on "io.sockets.on('connection', function(socket) {" 
// by express to socket.io using redis shared sessions
app.get('/', function (req, res) {
    req.io.socketDo('join', req.session.socketId, 'someRoomToJoin');

    // IT WORKS!
    req.io.sockets.in('someRoomToJoin').emit('text'); 

    req.io.socketDo('leave', req.session.socketId, 'someRoomToLeave');
    res.send('Hello World!');
});

remoteJoinremoteLeave 方法已添加到 socket.io-redis 3.0.0:

io.adapter.remoteJoin('<my-id>', 'room1', function (err) {
  if (err) { /* unknown id */ }
  // success
});

io.adapter.remoteLeave('<my-id>', 'room1', function (err) {
  if (err) { /* unknown id */ }
  // success
});

注意:实施看起来很像(希望如此?)答案