如何防止多个 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!),我能够使用
访问登录 cookiefunction 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 限制为每个事件只有一个侦听器?