如何防止多个 Socket.io 事件监听器

Posted

技术标签:

【中文标题】如何防止多个 Socket.io 事件监听器【英文标题】:How To Prevent Multiple Socket.io Event Listeners 【发布时间】:2017-12-07 07:46:39 【问题描述】:

我需要能够从我的 socket.io 事件监听器访问 req。所以我这样做了:

服务器

var express = require('express'),
    app = express(),

app.set('view engine', 'pug');

app.use(express.static(__dirname + '/public'));

client = require('socket.io').listen(8080).sockets;

app.get('/', function (req, res) 
    client.on('connection', function (socket) 
        console.log("Connection")
    );
    res.render('chat');
);

app.listen(config.server.port, function() 
    console.log("Listening on port " + config.server.port);
);

客户:

try 
        var socket = io.connect('http://127.0.0.1:8080');

     catch(e) 
        //Set status to warn user
        console.log(e);
    

问题在于,如果您在快速路由处理程序中添加 socket.io 事件侦听器,则会在套接字上创建多个侦听器。如果您要创建 pug 文件并测试此代码,您会注意到控制台在第一次刷新时记录“连接”一次,第二次刷新两次,依此类推,因为每次处理路由时都会添加另一个事件侦听器。我可以通过将侦听器移到路由处理程序之外来修复它,但是我需要能够访问“req”。是否有任何解决方案可以让我访问“req”并防止添加过多的侦听器?

【问题讨论】:

【参考方案1】:

不幸的是,socket.io connection 事件监听器中访问的req 对象是声明事件监听器时的req,而不是执行事件监听器时的req。因此,问题中提到的预期行为(如果我的理解是正确的)是不可能的。

这里是一个简单的实验,对于有问题的代码:

app.get('/', function (req, res) 
  client.on('connection', function (socket) 
    console.log("req.url: " + req.url)
  );
  res.render('chat');
);

如果使用浏览器发送 2 个 HTTP 请求:首先是 GET /?q=42,然后是 GET /?q=88,则 console.log 结果为:

//first request
req.url: /?q=42

//second request
req.url: /?q=42
req.url: /?q=88

对于第二个请求,由于connection 事件被监听了两次,事件监听器也会被执行两次。但是,执行结果不同——第一个 HTTP 请求中附加的事件侦听器记住当时的 req 对象值。


如果只有一个客户端,没有并发请求(非常受限的情况),有一个变通方法——将req保存为currentReq,并让事件监听器处理currentReq

var currentReq;
var isListened = false;

// write logic in middleware, so it can be used in all routes.
app.use(function(req, res, next) 
  currentReq = req;
  if (!isListened) 
    client.on('connection', function (socket) 
      console.log("req.url: " + currentReq.url)
    );
    isListened = true;
  
  next();
);

app.get('/', function (req, res) 
  res.render('chat');
);

这里有一些关于为什么这是不可能的想法。

问题中的场景是:

    浏览器向 Node.js 发送 HTTP GET 请求 Node.js 将 html 页面返回给浏览器 浏览器解析和呈现 HTML 页面 浏览器向 Node.js 发送 WebSocket 连接请求 Node.js建立WebSocket连接,并在控制台打印日志。

很明显,当connection 事件发生时(第 5 步),HTTP req(第 1 步)早已不复存在。第 5 步无法恢复初始 HTTP 请求信息,因为 WebSocket 和 HTTP 是不同的连接,在不同的端口上。

【讨论】:

如果这不可能,那么我如何确定用户是否登录到我的站点或不在侦听器内。截至目前,我只检查 req.user 是否存在,如果存在,那么我知道他们已登录。我希望某些套接字事件在处理它们之前确保用户已登录。 @Undying 判断用户是否登录,你可以发送 JWT token 或者 session id 作为 WebSocket 连接中的查询参数。这样,当 WebSocket 事件监听器被触发时,可以解码 JWT 令牌来检查用户是否仍然登录。或者,当 WebSocket 事件监听器被触发时,使用 session id 来检查会话是否仍然有效。跨度> @Undying 请查看***.com/questions/31680641/… 了解之前的一些讨论。【参考方案2】:

您可以通过控制台记录io.sockets来查看记录的事件监听器

Namespace 
  _events:
    connection: [ [Function], [Function], [Function], [Function] ] ,
  _eventsCount: 1 

要解决您的问题,只需在初始化套接字连接之前设置此条件

  if(io.sockets._events == undefined) 
    io.on('connection', socket => 
      ...
    );
  

万一除了connection之外还有其他事件监听器

  if(!('connection' in io.sockets._events)) 
    io.on('connection', socket => 
      ...
    );
  

【讨论】:

【参考方案3】:

聚会有点晚了,但似乎 OP 想要访问 req 对象以验证登录。为了将我的 2 便士加到组合中,这是我在类似情况下所做的:

我的应用使用基于 cookie 的登录令牌(仅通过 https!),我能够使用

访问登录 cookie
function cookieParser(cookief) 
    let cookies = 
    let _cookies = cookief.split("; ")
    for(cookiekv of cookief.split("; ")) 
        let kv = cookiekv.split("=")
        cookies[kv[0]] = kv[1]
    
    return cookies

function verifyLogin(cookies) 
    //verify login cookies here. Change this example
    return cookies.loginToken

io.use(function(socket,next) 
    const cookies = cookieParser(socket.handshake.headers.cookie)
    if(verifyLogin(cookies)) 
        next()
     else 
        next(new Error("invalid login token"))
     
)

这使我只能接受来自已登录并收到登录令牌的用户的套接字连接。这也应该可以解决您的多个侦听器方案,因为在 io 上只注册了一个侦听器。

希望对您有所帮助!

【讨论】:

以上是关于如何防止多个 Socket.io 事件监听器的主要内容,如果未能解决你的问题,请参考以下文章

多个侦听器和 removeListener 删除所有内容 Socket.io

如何检测未注册的socket.io事件?

是否可以将 socket.io 限制为每个事件只有一个侦听器?

刷新后socket.io客户端多次监听同一事件

Socket.io - 在 node.js 的单独文件中监听事件

socket.io 1.2.1 如何在重新连接后修复双重事件