当部署到 Google 云 运行 时,websockets 上的 MQTT NodeJS 服务器中的客户端定期断开连接
Periodic client disconnections in MQTT NodeJS server on websockets when deployed to Google Cloud Run
我有一个 Node JS MQTT 服务器 运行 在 websockets 上运行并部署到云端 运行。此服务器使用 Aedes MQTT Broker 库 (https://github.com/moscajs/aedes)。客户端连接到端口 443 上的此服务器。一切正常,但每个客户端定期与 MQTT Broker 断开连接(自每个客户端首次连接后大约每 5 分钟一次)
当服务器部署到专用机器或 Google 云 VM 时不会发生这种情况,仅当部署到 Google 云 运行.
时才会发生
为了说明这个问题,我在 NodeJS 中编写了一个超级简单的 MQTT 服务器,它允许任何客户端连接并回显队列“响应”客户端发送到队列“消息”的任何消息和一个 NodeJS 脚本到测试它。您可以在 https://github.com/madomingo/mqtt_test/tree/main
中找到完整的 2 文件项目
服务器代码是:
const aedes = require('aedes')()
const ws = require('websocket-stream')
const port = 8882
const WEB_SOCKETS = true
let httpServer
if (WEB_SOCKETS) {
httpServer = require('http').createServer()
ws.createServer({ server: httpServer }, aedes.handle)
} else {
httpServer = require('net').createServer(aedes.handle)
}
httpServer.listen(port, function () {
let type = (WEB_SOCKETS) ? "WebSockets" : "HTTP"
console.log('STARTED: ' + type + ' server listening on port ', port)
})
aedes.subscribe('messages', function (packet, callback) {
let msg = packet.payload.toString()
console.log('Received message', msg);
aedes.publish(
{
topic: 'responses',
payload: msg,
retain: false
}
);
});
我还有一个连接到服务器并每 15 秒将日期时间发送到队列的 NodeJS 测试脚本:
let mqtt = require('mqtt');
// Web sockets host
const host = "ws://localhost:8882"
let clientId = 'test web client ' + Date.now
let client = mqtt.connect(host, {
clientId: clientId
});
client.on('connect', function () {
console.log("Connected to server")
client.subscribe('responses');
sendMessage()
setInterval(() => {
sendMessage()
}, 15000);
})
client.on('message', function (topic, message) {
console.log('Received a message in topic: ' + topic + ": " + message.toString());
});
client.on('error', function(e) {
console.log('Received an error: ' + e.message)
})
client.on('disconnect', function() {
console.log('Client was disconnected ' )
})
client.on('reconnect', function() {
console.log('Client was reconnected ')
})
client.on('close', function() {
console.log('Client was closed ' )
})
client.on('offline', function() {
console.log('Client was offline ')
})
function sendMessage() {
let now = new Date().toISOString()
let msg = "Current time = " + now
console.log("Publishing data", msg)
client.publish('messages', msg);
}
如果我 运行 服务器和本地测试脚本,一切都按预期工作:每 15 秒,客户端将日期时间发送到队列“消息”中的服务器,然后服务器立即回显此日期时间以排队“响应”。服务器和客户端都登录到控制台。这 运行 无限期且不会中断。
我发现的问题是当服务器部署到 Google 云 运行 服务时(我已经这样做了并且我得到了 url: test-mqtt-server- kdtisjwi5a-ew.a.run.app).
在测试脚本中,我将主机url更改为:const host = "wss://test-mqtt-server-kdtisjwi5a-ew.a.run.app:443"。然后 运行 再次测试脚本,客户端连接到部署到云 运行 的服务器,一段时间后,控制台按预期显示日志:
Connected to server
Publishing data Current time = 2021-09-16T16:06:26.937Z
Received a message in topic: responses: Current time = 2021-09-16T16:06:26.937Z
Publishing data Current time = 2021-09-16T16:06:41.944Z
Received a message in topic: responses: Current time = 2021-09-16T16:06:41.944Z
但是在第一次连接后每隔 5 分钟,客户端就会意外断开连接,尽管它会立即重新连接。日志现在显示:
Received a message in topic: responses: Current time = 2021-09-16T16:11:11.975Z
Client was offline
Client was closed
Client was reconnected
Connected to server
Publishing data Current time = 2021-09-16T16:11:28.121Z
Received a message in topic: responses: Current time = 2021-09-16T16:11:28.121Z
我没想到会断开连接,因为显然没有任何连接问题。所以我的问题是,为什么在部署到 Cloud 运行 时会发生这种断开连接?可以避免吗?
感谢任何帮助,谢谢!!
根据评论 docs 说:
WebSockets requests are treated as long-running HTTP requests in Cloud Run. They are subject to request timeouts (currently up to 60 minutes and defaults to 5 minutes) even if your application server does not enforce any timeouts.
Accordingly, if the client keeps the connection open longer than the required timeout configured for the Cloud Run service, the client will be disconnected when the request times out.
Therefore, WebSockets clients connecting to Cloud Run should handle reconnecting to the server if the request times out or the server disconnects. You can achieve this in browser-based clients by using libraries such as reconnecting-websocket or by handling "disconnect" events if you are using the SocketIO library.
这将适用于 WebSockets 上的 MQTT,因此更改超时将允许更长的连接,但您还应确保客户端在连接断开时自动重新连接。
我有一个 Node JS MQTT 服务器 运行 在 websockets 上运行并部署到云端 运行。此服务器使用 Aedes MQTT Broker 库 (https://github.com/moscajs/aedes)。客户端连接到端口 443 上的此服务器。一切正常,但每个客户端定期与 MQTT Broker 断开连接(自每个客户端首次连接后大约每 5 分钟一次) 当服务器部署到专用机器或 Google 云 VM 时不会发生这种情况,仅当部署到 Google 云 运行.
时才会发生为了说明这个问题,我在 NodeJS 中编写了一个超级简单的 MQTT 服务器,它允许任何客户端连接并回显队列“响应”客户端发送到队列“消息”的任何消息和一个 NodeJS 脚本到测试它。您可以在 https://github.com/madomingo/mqtt_test/tree/main
中找到完整的 2 文件项目服务器代码是:
const aedes = require('aedes')()
const ws = require('websocket-stream')
const port = 8882
const WEB_SOCKETS = true
let httpServer
if (WEB_SOCKETS) {
httpServer = require('http').createServer()
ws.createServer({ server: httpServer }, aedes.handle)
} else {
httpServer = require('net').createServer(aedes.handle)
}
httpServer.listen(port, function () {
let type = (WEB_SOCKETS) ? "WebSockets" : "HTTP"
console.log('STARTED: ' + type + ' server listening on port ', port)
})
aedes.subscribe('messages', function (packet, callback) {
let msg = packet.payload.toString()
console.log('Received message', msg);
aedes.publish(
{
topic: 'responses',
payload: msg,
retain: false
}
);
});
我还有一个连接到服务器并每 15 秒将日期时间发送到队列的 NodeJS 测试脚本:
let mqtt = require('mqtt');
// Web sockets host
const host = "ws://localhost:8882"
let clientId = 'test web client ' + Date.now
let client = mqtt.connect(host, {
clientId: clientId
});
client.on('connect', function () {
console.log("Connected to server")
client.subscribe('responses');
sendMessage()
setInterval(() => {
sendMessage()
}, 15000);
})
client.on('message', function (topic, message) {
console.log('Received a message in topic: ' + topic + ": " + message.toString());
});
client.on('error', function(e) {
console.log('Received an error: ' + e.message)
})
client.on('disconnect', function() {
console.log('Client was disconnected ' )
})
client.on('reconnect', function() {
console.log('Client was reconnected ')
})
client.on('close', function() {
console.log('Client was closed ' )
})
client.on('offline', function() {
console.log('Client was offline ')
})
function sendMessage() {
let now = new Date().toISOString()
let msg = "Current time = " + now
console.log("Publishing data", msg)
client.publish('messages', msg);
}
如果我 运行 服务器和本地测试脚本,一切都按预期工作:每 15 秒,客户端将日期时间发送到队列“消息”中的服务器,然后服务器立即回显此日期时间以排队“响应”。服务器和客户端都登录到控制台。这 运行 无限期且不会中断。
我发现的问题是当服务器部署到 Google 云 运行 服务时(我已经这样做了并且我得到了 url: test-mqtt-server- kdtisjwi5a-ew.a.run.app).
在测试脚本中,我将主机url更改为:const host = "wss://test-mqtt-server-kdtisjwi5a-ew.a.run.app:443"。然后 运行 再次测试脚本,客户端连接到部署到云 运行 的服务器,一段时间后,控制台按预期显示日志:
Connected to server
Publishing data Current time = 2021-09-16T16:06:26.937Z
Received a message in topic: responses: Current time = 2021-09-16T16:06:26.937Z
Publishing data Current time = 2021-09-16T16:06:41.944Z
Received a message in topic: responses: Current time = 2021-09-16T16:06:41.944Z
但是在第一次连接后每隔 5 分钟,客户端就会意外断开连接,尽管它会立即重新连接。日志现在显示:
Received a message in topic: responses: Current time = 2021-09-16T16:11:11.975Z
Client was offline
Client was closed
Client was reconnected
Connected to server
Publishing data Current time = 2021-09-16T16:11:28.121Z
Received a message in topic: responses: Current time = 2021-09-16T16:11:28.121Z
我没想到会断开连接,因为显然没有任何连接问题。所以我的问题是,为什么在部署到 Cloud 运行 时会发生这种断开连接?可以避免吗?
感谢任何帮助,谢谢!!
根据评论 docs 说:
WebSockets requests are treated as long-running HTTP requests in Cloud Run. They are subject to request timeouts (currently up to 60 minutes and defaults to 5 minutes) even if your application server does not enforce any timeouts.
Accordingly, if the client keeps the connection open longer than the required timeout configured for the Cloud Run service, the client will be disconnected when the request times out.
Therefore, WebSockets clients connecting to Cloud Run should handle reconnecting to the server if the request times out or the server disconnects. You can achieve this in browser-based clients by using libraries such as reconnecting-websocket or by handling "disconnect" events if you are using the SocketIO library.
这将适用于 WebSockets 上的 MQTT,因此更改超时将允许更长的连接,但您还应确保客户端在连接断开时自动重新连接。