【问题标题】:How To Prevent Multiple Socket.io Event Listeners如何防止多个 Socket.io 事件监听器
【发布时间】: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”并防止添加过多的侦听器?

【问题讨论】:

    标签: javascript node.js express socket.io


    【解决方案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');
    });
    

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

    问题中的场景是:

    1. 浏览器向 Node.js 发送 HTTP GET 请求
    2. Node.js 将 HTML 页面返回给浏览器
    3. 浏览器解析和呈现 HTML 页面
    4. 浏览器向 Node.js 发送 WebSocket 连接请求
    5. 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 请查看stackoverflow.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 上只注册了一个侦听器。

      希望对您有所帮助!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-14
        • 1970-01-01
        • 1970-01-01
        • 2015-11-22
        相关资源
        最近更新 更多