草庐IT

javascript - Socket.io 意外断开连接

coder 2025-03-21 原文

我有 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

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

  1. 前端启动套接字连接并向后端发送初始化消息。它还保存了套接字。
  2. 后端检测到连接并收到初始化消息
  3. 后端把socket放到数组中,随时随地都可以使用
  4. 第一个套接字意外断开连接,并且在前端不知情的情况下发布了另一个连接,因此前端从不发送消息来初始化它。
  5. 由于前端保存的套接字没有改变,它在发出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/

有关javascript - Socket.io 意外断开连接的更多相关文章

  1. ruby-openid:执行发现时未设置@socket - 2

    我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

  2. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  3. ruby - 续集在添加关联时访问many_to_many连接表 - 2

    我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以

  4. ruby - 无法在 60 秒内获得稳定的 Firefox 连接 (127.0.0.1 :7055) - 2

    我使用的是Firefox版本36.0.1和Selenium-Webdrivergem版本2.45.0。我能够创建Firefox实例,但无法使用脚本继续进行进一步的操作无法在60秒内获得稳定的Firefox连接(127.0.0.1:7055)错误。有人能帮帮我吗? 最佳答案 我遇到了同样的问题。降级到firefoxv33后一切正常。您可以找到旧版本here 关于ruby-无法在60秒内获得稳定的Firefox连接(127.0.0.1:7055),我们在StackOverflow上找到一个类

  5. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  6. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  7. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  8. ruby - 我的 Ruby IRC 机器人没有连接到 IRC 服务器。我究竟做错了什么? - 2

    require"socket"server="irc.rizon.net"port="6667"nick="RubyIRCBot"channel="#0x40"s=TCPSocket.open(server,port)s.print("USERTesting",0)s.print("NICK#{nick}",0)s.print("JOIN#{channel}",0)这个IRC机器人没有连接到IRC服务器,我做错了什么? 最佳答案 失败并显示此消息::irc.shakeababy.net461*USER:Notenoughparame

  9. Ruby - 如何处理子类意外覆盖父类(super class)私有(private)字段的问题? - 2

    假设您编写了一个类Sup,我决定将其扩展为SubSup。我不仅需要了解你发布的接口(interface),还需要了解你的私有(private)字段。见证这次失败:classSupdefinitialize@privateField="fromsup"enddefgetXreturn@privateFieldendendclassSub问题是,解决这个问题的正确方法是什么?看起来子类应该能够使用它想要的任何字段而不会弄乱父类(superclass)。编辑:equivalentexampleinJava返回"fromSup",这也是它应该产生的答案。 最佳答案

  10. ruby - 为什么不能使用类IO的实例方法noecho? - 2

    print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上

随机推荐