我有 node.js 服务和 Angular 客户端,使用 socket.io 在长时间的 http 请求期间传输一些消息。
服务:
export const socketArray: SocketIO.Socket[] = [];
export let socketMapping: {[socketId: string]: number} = {};
const socketRegister: hapi.Plugin<any> = {
register: (server) => {
const io: SocketIO.Server = socket(server.listener);
// Whenever a session connected to socket, create a socket object and add it to socket array
io.on("connection", (socket) => {
console.log(`socket ${socket.id} connected`);
logger.info(`socket ${socket.id} connected`);
// Only put socket object into array if init message received
socket.on("init", msg => {
logger.info(`socket ${socket.id} initialized`);
socketArray.push(socket);
socketMapping[socket.id] = msg;
});
// Remove socket object from socket array when disconnected
socket.on("disconnect", (reason) => {
console.log(`socket ${socket.id} disconnected because: ${reason}`)
logger.info(`socket ${socket.id} disconnected because: ${reason}`);
for(let i = 0; i < socketArray.length; i ++) {
if(socketArray[i] === socket) {
socketArray.splice(i, 1);
return;
}
}
});
});
},
name: "socketRegister",
version: "1.0"
}
export const socketSender = async (socketId: string, channel: string, content: SocketMessage) => {
try {
// Add message to db here
// await storeMessage(socketMapping[socketId], content);
// Find corresponding socket and send message
logger.info(`trying sending message to ${socketId}`);
for (let i = 0; i < socketArray.length; i ++) {
if (socketArray[i].id === socketId) {
socketArray[i].emit(channel, JSON.stringify(content));
logger.info(`socket ${socketId} send message to ${channel}`);
if (content.isFinal == true) {
// TODO: delete all messages of the process if isFinal is true
await deleteProcess(content.processId);
}
return;
}
}
} catch (err) {
logger.error("Socket sender error: ", err.message);
}
};
客户:
connectSocket() {
if (!this.socket) {
try {
this.socket = io(socketUrl);
this.socket.emit('init', 'some-data');
} catch (err) {
console.log(err);
}
} else if (this.socket.disconnected) {
this.socket.connect();
this.socket.emit('init', 'some-data');
}
this.socket.on('some-channel', (data) => {
// Do something
});
this.socket.on('disconnect', (data) => {
console.log(data);
});
}
它们通常工作正常但会随机产生断开连接错误。从我的日志文件中,我们可以看到:
2018-07-21T00:20:28.209Z[x]INFO: socket 8jBh7YC4A1btDTo_AAAN connected
2018-07-21T00:20:28.324Z[x]INFO: socket 8jBh7YC4A1btDTo_AAAN initialized
2018-07-21T00:21:48.314Z[x]INFO: socket 8jBh7YC4A1btDTo_AAAN disconnected because: ping timeout
2018-07-21T00:21:50.849Z[x]INFO: socket C6O7Vq38ygNiwGHcAAAO connected
2018-07-21T00:23:09.345Z[x]INFO: trying sending message to C6O7Vq38ygNiwGHcAAAO
在断开连接消息的同时,前端还注意到一个断开连接事件,上面写着transport close
。
从日志中,我们可以得到工作流程是这样的:
- 前端启动套接字连接并向后端发送初始化消息。它还保存了套接字。
- 后端检测到连接并收到初始化消息
- 后端把socket放到数组中,随时随地都可以使用
- 第一个套接字意外断开连接,并且在前端不知情的情况下发布了另一个连接,因此前端从不发送消息来初始化它。
- 由于前端保存的套接字没有改变,它在发出http请求时使用旧的套接字ID。结果,后端使用已从套接字数组中删除的旧套接字发送消息。
这种情况并不经常发生。有谁知道什么可能导致断开连接和未知连接问题?
最佳答案
这真的取决于“长时间的 http 请求”在做什么。 node.js 将您的 Javascript 作为单线程运行。这意味着它一次只能做一件事。但是,由于服务器所做的很多事情都与 I/O 相关(从数据库读取、从文件中获取数据、从另一台服务器获取数据等)并且 node.js 使用事件驱动的异步 I/O,它通常可以同时有很多球在空中,所以它似乎同时处理很多请求。
但是,如果您的复杂 http 请求是 CPU 密集型请求,使用大量 CPU,那么它就会占用单个 Javascript 线程,并且在占用 CPU 时无法完成其他任何事情。这意味着所有传入的 HTTP 或 socket.io 请求都必须在队列中等待,直到一个 node.js Javascript 线程空闲,这样它才能从事件队列中获取下一个事件并开始处理传入的请求。
只有当我们能够看到这个“非常复杂的 http 请求”的代码时,我们才能真正为您提供更具体的帮助。
在 node.js 中解决 CPU 占用问题的通常方法是将 CPU 密集型内容卸载到其他进程。如果主要是这一段代码导致了问题,您可以启动几个子进程(可能与您服务器中的 CPU 数量一样多),然后为它们提供 CPU 密集型工作并离开您的主 Node .js 进程可以自由处理传入(非 CPU 密集型)请求,延迟非常低。
如果您有多个操作可能会占用 CPU,那么您要么必须将它们全部分配给子进程(可能通过某种工作队列),要么您可以部署集群。集群的挑战在于,给定的 socket.io 连接将指向集群中的一个特定服务器,如果该进程恰好正在执行占用 CPU 的操作,那么分配给该服务器的所有 socket.io 连接都将有不好的延迟。因此,常规聚类可能不太适合此类问题。处理 CPU 密集型工作的工作队列和多个专用子进程可能更好,因为这些进程不会有任何它们负责的外部 socket.io 连接。
此外,您应该知道,如果您使用的是同步文件 I/O,则会阻塞整个 node.js Javascript 线程。 node.js 在同步文件 I/O 操作期间不能运行任何其他 Javascript。 node.js 从其异步 I/O 模型中获得了可扩展性和同时进行许多操作的能力。如果您使用同步 I/O,就会完全破坏它并破坏可伸缩性和响应能力。
同步文件 I/O 仅属于服务器启动代码或单一用途脚本(而非服务器)。在服务器中处理请求时不应使用它。
使异步文件 I/O 更容易接受的两种方法是使用流或使用 async/await
和 promise 的 fs
方法。
关于javascript - Socket.io 意外断开连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51451937/