我正在尝试编写一个简单的node.js服务器,具有服务器发送事件功能,无需socket.io。我的代码运行得很好,但是在测试时遇到了一个很大的问题。
如果我从浏览器连接到 node.js 服务器,它将正常接收服务器事件。但是当我刷新连接时,同一个浏览器将开始接收该事件两次。如果我刷新n次,浏览器就会收到n+1次数据。
在我停止尝试计数之前,我最多尝试了 16 次(16 次刷新)。
从我的浏览器查看下面的控制台屏幕截图。刷新后(尝试进行AJAX调用),会输出“SSE持久连接已建立!”然后它将开始接收事件。
注意事件到达的时间。我在 19:34:07 两次收到该事件(它两次记录到控制台 - 在收到事件时以及在将数据写入屏幕时;因此您可以在那里看到四个日志)。
我还在 19:34:12 两次收到该事件。
这是客户端关闭连接(使用 source.close() )后服务器端的样子:
如您所见,它仍在尝试向客户端发送消息!另外,它尝试发送消息两次(所以我知道这是服务器端问题)!
我知道它尝试发送两次,因为它发送了两次心跳,而它应该每 30 秒发送一次。
当打开 n 个选项卡时,此问题会加剧。发生的情况是每个打开的选项卡都会收到 n*n 事件。基本上,我的解释是这样的:
打开第一个选项卡,我订阅服务器一次。打开第二个选项卡,我再次向服务器订阅这两个打开的选项卡 - 因此每个选项卡有 2 个订阅。打开第三个选项卡,我再次订阅所有三个打开的选项卡的事件,因此每个选项卡 3 个订阅,总共 9 个。依此类推...
我无法验证这一点,但我的猜测是,如果我可以创建一个订阅,那么如果满足某些条件(即心跳失败,我必须断开连接),我应该能够取消订阅。发生这种情况的原因仅在于以下几点:
- 我启动了一次 setInterval,除非我停止它,否则它的一个实例将永远运行,或者
- 服务器仍在尝试向客户端发送数据并试图保持连接打开?
至于1.,我已经尝试用clearInterval来终止setInterval,但它不起作用。至于2,虽然这可能是不可能的,但我倾向于相信......
这是相关部分的服务器端代码片段(根据答案的建议编辑代码):
server = http.createServer(function(req, res){
heartbeatPulse(res);
}).listen(8888, '127.0.0.1', 511);
server.on("request", function(req, res){
console.log("Request Received!");
var route_origin = url.parse(req.url);
if(route_origin.query !== null){
serveAJAX(res);
} else {
serveSSE(res);
//hbTimerID = timerID.hbID;
//msgTimerID = timerID.msgID;
}
req.on("close", function(){
console.log("close the connection!");
res.end();
clearInterval(res['hbID']);
clearInterval(res['msgID']);
console.log(res['hbID']);
console.log(res['msgID']);
});
});
var attachListeners = function(res){
/*
Adding listeners to the server
These events are triggered only when the server communicates by SSE
*/
// this listener listens for file changes -- needs more modification to accommodate which file changed, what to write
server.addListener("file_changed", function(res){
// replace this with a file reader function
writeUpdate = res.write("data: {\"date\": \"" + Date() + "\", \"test\": \"nowsssss\", \"i\": " + i++ + "}\n\n");
if(writeUpdate){
console.log("Sent SSE!");
} else {
console.log("SSE failed to send!");
}
});
// this listener enables the server to send heartbeats
server.addListener("hb", function(res){
if(res.write("event: hb\ndata:\nretry: 5000\n\n")){
console.log("Sent HB!");
} else {
// this fails. We can't just close the response, we need to close the connection
// how to close a connection upon the client closing the browser??
console.log("HB failed! Closing connection to client...");
res.end();
//console.log(http.IncomingMessage.connection);
//http.IncomingMessage.complete = true;
clearInterval(res['hbID']);
clearInterval(res['msgID']);
console.log(res['hbID']);
console.log(res['msgID']);
console.log("\tConnection Closed.");
}
});
}
var heartbeatPulse = function(res){
res['hbID'] = "";
res['msgID'] = "";
res['hbID'] = setInterval(function(){
server.emit("hb", res);
}, HB_period);
res['msgID'] = setInterval(function(){
server.emit("file_changed", res);
}, 5000);
console.log(res['hbID']);
console.log(res['msgID'])
/*return {
hbID: hbID,
msgID: msgID
};*/
}
var serveSSE = function(res){
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Access-Control-Allow-Origin": "*",
"Connection": "keep-alive"
});
console.log("Establishing Persistent Connection...");
if(res.write("event: connector\ndata:\nretry: 5000\n\n")){
// Only upon receiving the first message will the headers be sent
console.log("Established Persistent Connection!");
}
attachListeners(res);
console.log("\tRequested via SSE!");
}
这很大程度上是一个 self 学习项目,因此欢迎任何评论。
最佳答案
一个问题是您将请求特定的变量存储在 http 服务器的范围之外。因此,您可以做的就是在启动 http 服务器后立即调用 setInterval()
一次,而不是针对每个请求启动单独的计时器。
为每个请求添加事件处理程序的另一种方法可能是将响应对象添加到在每个 setInterval()
回调内部循环的数组中,写入响应。当连接关闭时,从数组中删除响应对象。
有关检测死连接的第二个问题可以通过监听 close
event 来解决在 req
对象上。发出该消息后,您将删除为该连接添加的服务器事件(例如 file_changed
和 hb
)监听器,并执行任何其他必要的清理操作。
关于javascript - 当客户端浏览器退出时如何终止node.js服务器连接?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26382307/