Socket.io意外断开连接

时间:2018-07-21 00:57:32

标签: javascript node.js angular websocket socket.io

我有node.js服务和使用socket.io的angular客户在长时间的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

从日志中,我们可以得到的工作流程是这样的:

  1. 前端开始套接字连接,并向后端发送初始化消息。它还可以保存套接字。
  2. 后端检测到连接并收到初始化消息
  3. 后端将套接字放置到阵列中,以便可以随时随地使用
  4. 第一个套接字意外断开连接,并且另一个连接在前端不觉察的情况下发布,因此前端从不发送消息来初始化它。
  5. 由于前端的已保存套接字未更改,因此在发出http请求时使用了旧的套接字ID。结果,后端使用已从套接字阵列中删除的旧套接字发送了消息。

这种情况不会经常发生。有谁知道是什么原因可能导致断开连接和未知的连接问题?

1 个答案:

答案 0 :(得分:6)

这实际上取决于“长时间的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工作并离开主节点.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更具容忍度的两种方法是使用流或将async/await与承诺的fs方法一起使用。