Socket.io意外断开连接。

7
我有一个使用socket.io在长时间的HTTP请求期间传输一些消息的node.js服务和angular客户端。
服务:
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”的断开事件。
从日志中我们可以看到工作流程如下:
1. 前端启动了一个 socket 连接并向后端发送了一个初始化消息。它也保存了这个 socket。 2. 后端检测到了连接并收到了初始化消息。 3. 后端将该 socket 放入数组中,以便随时随地使用。 4. 第一个 socket 意外断开连接,另一个连接被发布,前端不知情,因此前端从未发送过初始化消息。 5. 由于前端保存的 socket 没有改变,当它进行 http 请求时,它使用了旧的 socket ID。结果,后端使用已从 socket 数组中删除的旧 socket 发送了消息。
这种情况并不经常发生。有人知道是什么原因导致了断开连接和未知连接问题吗?
1个回答

7
"长时间的http请求"究竟在做什么,这很大程度上取决于具体情况。Node.js使用单线程运行JavaScript代码,这意味着它一次只能做一件事情。但是,因为服务器执行的许多操作都涉及I/O(从数据库读取数据,从文件获取数据,从另一个服务器获取数据等等),Node.js使用事件驱动的异步I/O,因此它通常可以同时处理多个请求,看起来像是同时处理许多请求。
但是,如果你的复杂的http请求需要大量的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线程。在同步文件I/O操作期间,Node.js无法运行任何其他JavaScript代码。Node.js能够具有可扩展性和同时进行多个操作的能力,正是因为其采用了异步I/O模式。如果使用同步I/O,则将完全破坏这一优势,影响服务器的可扩展性和响应速度。
同步文件I/O仅适用于服务器启动代码或单个目的脚本(而不是服务器)。在服务器处理请求时,不应使用同步I/O。
使异步文件I/O更容易处理的两种方法是使用流或使用带有Promisified方法的async/await。

@zhangjinzhou - 我添加了一些关于同步文件I/O的更多信息。 - jfriend00

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接