如何在Node.js中调试ECONNRESET错误?

432

我正在运行一个使用Socket.io的Express.js应用程序,用于聊天Web应用程序,并且在24小时内随机出现大约5次以下错误。 Node处理过程被永久包装并立即重新启动。

问题是重新启动Express会将我的用户踢出他们的聊天室,这是没有人想要的。

Web服务器由HAProxy代理。没有套接字稳定性问题,只是使用Websockets和Flashsockets传输方式。我无法故意复制此错误。

这是使用Node v0.10.11时出现的错误:

    events.js:72
            throw er; // Unhandled 'error' event
                  ^
    Error: read ECONNRESET     //alternatively it s a 'write'
        at errnoException (net.js:900:11)
        at TCP.onread (net.js:555:19)
    error: Forever detected script exited with code: 8
    error: Forever restarting script for 2 time

编辑(2013-07-22)

添加了socket.io客户端错误处理程序和未捕获异常处理程序。 似乎这个能够捕获错误:

    process.on('uncaughtException', function (err) {
      console.error(err.stack);
      console.log("Node NOT Exiting...");
    });

所以我怀疑这不是Socket.io的问题,而是我进行HTTP请求到另一个服务器或者MySQL/Redis连接时出现的问题。问题是错误堆栈并不能帮助我确定我的代码问题。这里是日志输出:

    Error: read ECONNRESET
        at errnoException (net.js:900:11)
        at TCP.onread (net.js:555:19)

我该如何知道是什么原因导致这个问题?如何更好地利用这个错误?

好的,虽然不是非常详细,但这是使用 Longjohn 的堆栈跟踪:

    Exception caught: Error ECONNRESET
    { [Error: read ECONNRESET]
      code: 'ECONNRESET',
      errno: 'ECONNRESET',
      syscall: 'read',
      __cached_trace__:
       [ { receiver: [Object],
           fun: [Function: errnoException],
           pos: 22930 },
         { receiver: [Object], fun: [Function: onread], pos: 14545 },
         {},
         { receiver: [Object],
           fun: [Function: fireErrorCallbacks],
           pos: 11672 },
         { receiver: [Object], fun: [Function], pos: 12329 },
         { receiver: [Object], fun: [Function: onread], pos: 14536 } ],
      __previous__:
       { [Error]
         id: 1061835,
         location: 'fireErrorCallbacks (net.js:439)',
         __location__: 'process.nextTick',
         __previous__: null,
         __trace_count__: 1,
         __cached_trace__: [ [Object], [Object], [Object] ] } }

我在这里提供Flash套接字策略文件:

    net = require("net")
    net.createServer( (socket) =>
      socket.write("<?xml version=\"1.0\"?>\n")
      socket.write("<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n")
      socket.write("<cross-domain-policy>\n")
      socket.write("<allow-access-from domain=\"*\" to-ports=\"*\"/>\n")
      socket.write("</cross-domain-policy>\n")
      socket.end()
    ).listen(843)

这能是原因吗?


4
@GottZ 或许这可以帮到你(与 Node.js 相关的某个人士交流后得到的信息) https://gist.github.com/samsonradu/1b0c6feb438f5a53e30e。我今天将部署 socket.error 处理程序并告诉你。 - Samson
1
@Gottz,socket.error 处理无效,但是 process.on('uncaughtException') 可以捕获错误。这里是错误的 console.log:{ [Error: read ECONNRESET] code: 'ECONNRESET', errno: 'ECONNRESET', syscall: 'read' }。 - Samson
1
ECONNRESET 可能来自网络问题。正如您所知,在测试时不可能捕获所有异常。有些异常会在生产服务器上显示出来。您需要使您的服务器更加健壮。您可以使用 Redis 作为存储来处理会话删除。这样可以使您的会话持久化,即使您的节点服务器宕机也不会丢失。 - user568109
2
为什么这与会话删除有关?它们无论如何都由Redis处理。 - Samson
3
你至少有一个TCP套接字正在侦听,但没有设置处理程序。现在是时候检查它在哪里了 :D - Moss
显示剩余9条评论
19个回答

380

你可能已经猜到了:这是一个连接错误。

"ECONNRESET"表示TCP对话的另一端突然关闭了连接的一端。这很可能是由于一个或多个应用程序协议错误引起的。你可以查看API服务器日志,看它是否抱怨了什么。

但由于你还在寻找一种检查错误并潜在地调试问题的方法,你应该看一下How to debug a socket hang up error in NodeJS?,这篇文章是在stackoverflow上发布的,与类似的问题有关。

对于开发而言的快速且简单的解决方案

使用longjohn,你会得到包含异步操作的长堆栈跟踪。

干净、正确的解决方案

技术上,在node中,每当你发出一个“错误”事件并且没有人监听它,它就会抛出异常。为了使它不抛出异常,给它添加一个监听器并自己处理它。这样你就可以记录更多信息的错误日志了。

为了在一组调用中拥有一个监听器,可以使用domains并在运行时捕获其他错误。请确保与代码的其他部分相比,与http(服务器/客户端)相关的每个异步操作都在不同的domain上下文中,该域将自动侦听error事件,并将其传播到其自己的处理程序。因此,您只需侦听该处理程序并获取错误数据。您还可以免费获取更多信息。

编辑(2013-07-22)

就像我上面写的:

"ECONNRESET" 意味着TCP对话的另一侧突然关闭了连接的一端。这很可能是由于一个或多个应用程序协议错误引起的。您可以查看API服务器日志,看看它是否抱怨了什么。

还可能的情况是:随机的时间,另一侧过载并因此终止连接。如果是这种情况,则取决于您要连接的内容...

但有一件事是确定的:您确实在TCP连接上遇到了读取错误,这导致了异常。通过查看您在编辑中发布的错误代码,可以确认这一点。


它不一定意味着“突然关闭”。通常是由于向已经正常关闭的连接写入数据导致的。这将导致它发出一个RST信号。 - user207421
5
@EJP,我使用“突然”这个词是有原因的。错误(不是警告)显示连接被对等方重置。现有连接被远程对等方强制关闭。由于这种关闭是意外发生的,所以是突然的!(通常情况下,如果远程机器上的对等应用程序突然停止、机器重新启动或对等应用程序在远程套接字上使用了“硬关闭”,则会导致此类强制关闭。如果由于“保持活动”检测到故障而导致连接中断,则可能也会导致此类错误,此时一个或多个操作会失败...这些操作和后续操作将失败。) - e-sushi
2
当我在浏览器(Chrome)中批量发送大约100个API调用进行测试时,会抛出此错误。我想Chrome必须变得过载并杀死一些连接...@Samson - 处理每个请求的域并捕获域错误而不重新启动服务器有什么问题吗? - supershnee
3
在出现未捕获的异常后,你几乎总是应该重新启动服务器,因为你的数据、应用程序和 Node.js 本身处于未知状态。在异常发生后继续运行会使你的数据处于风险之中。如果想了解更多信息,请查看Node关于process的文档Node关于domains的文档 - c1moore
这个错误会在生产环境中出现吗? - Rajesh Khadka

59

我运行的一个简单TCP服务器用于提供Flash策略文件,导致了这个问题。现在,我可以使用处理程序来捕获错误:

# serving the flash policy file
net = require("net")

net.createServer((socket) =>
  //just added
  socket.on("error", (err) =>
    console.log("Caught flash policy server socket error: ")
    console.log(err.stack)
  )

  socket.write("<?xml version=\"1.0\"?>\n")
  socket.write("<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n")
  socket.write("<cross-domain-policy>\n")
  socket.write("<allow-access-from domain=\"*\" to-ports=\"*\"/>\n")
  socket.write("</cross-domain-policy>\n")
  socket.end()
).listen(843)

3
代码有什么问题吗?在写入之前,我应该检查套接字是否可写吗? - Samson
1
哦,没看到你在我发了几乎相同的东西之前已经找到了解决方案 :) 至于你的问题,即使你检查了套接字是否可写,在微秒后写入它时可能仍然不可写并且会抛出错误,所以这是“确定”的方法。 - Joachim Isaksson
好的,如果这样有安全的解决方法吗?比如在错误处理程序中使用socket.close()?因为我认为这些错误发生后我的CPU负载正在增加(不确定)。 - Samson
3
我一直在错误处理程序中调用 socket.destroy() 以确保关闭套接字。遗憾的是我找不到相关的文档说明它是否是必需的,但这样做不会触发错误。 - Joachim Isaksson
socket.destroy() 保住了我的一天,不管它是如何工作的!!谢谢! - Firas Abd Alrahman

38

我曾遇到一个类似的问题,在升级 Node 后应用程序开始出现错误。我认为这可以追溯到 Node 版本 v0.9.10 中的以下问题:

  • net: 不要抑制 ECONNRESET (Ben Noordhuis)

之前的版本不会在客户端中断时报错。当客户端连接中断时,Node 会报 ECONNRESET 错误。我认为这是 Node 的预期功能,因此解决方案(至少对我来说)是处理错误,我认为您在未捕获异常中已经做到了这一点。尽管我将其处理在 net.socket 处理程序中。

您可以演示以下内容:

创建一个简单的 socket 服务器并获取 Node v0.9.9 和 v0.9.10。

require('net')
    .createServer( function(socket) 
    {
           // no nothing
    })
    .listen(21, function()
     {
           console.log('Socket ON')
    })

使用v0.9.9启动它,然后尝试FTP到此服务器。 我仅使用FTP和端口21,因为我在Windows上,并且有一个FTP客户端,但没有telnet客户端。

然后从客户端断开连接。(我只是按Ctrl-C)

当使用Node v0.9.9时,应该看不到错误,而使用Node v.0.9.10及更高版本会显示错误。

在生产中,我使用v.0.10.x仍然会出现错误。再次强调,我认为这是故意的,解决方案是在您的代码中处理错误。


5
谢谢,这是我自己完成的! 不让错误传播到未捕获的异常很重要,因为它会使整个应用程序变得不稳定。例如,在捕获了约10个ECONNRESET错误后,服务器有时会变得无响应(只是冻结并且没有处理任何连接)。 - Samson
2
我也知道节点版本的更改不再抑制错误,但是考虑到每个版本都会出现许多问题并得到解决,我宁愿选择最新版本。顺便说一下,我现在正在使用V0.10.13。 - Samson

25

今天我也遇到了同样的问题。经过一些研究,我发现一个非常有用的--abort-on-uncaught-exception Node.js选项。它不仅提供了更详细、更有用的错误堆栈跟踪,还在应用程序崩溃时保存核心文件,以便进一步调试。


4
很奇怪,在我查看时一个新的答案出现在这个老问题上--不过这非常棒,谢谢。 - Semicolon

19

在我的开发过程中,我也遇到了 ECONNRESET 错误,解决方法是不要使用 nodemon 启动服务器,只需使用 "node server.js" 命令来启动服务器即可修复该问题。

虽然有些奇怪,但对我有效,现在我再也没有看到 ECONNRESET 错误了。


你有什么想法是怎么想出这个解决方案的吗?你只是随机尝试了一下。这也帮助了我。 - Riza Khan

16

我遇到了同样的问题,但我通过放置以下内容来减轻这个问题:

server.timeout = 0;

server.listen之前。这里的server是一个HTTP服务器。根据API文档,默认超时时间为2分钟。


8
这不是一个解决方案,而是一种快速修复方法,它会在没有抛出错误的情况下破坏事物。 - Nishant Ghodke
我认为对我来说问题在于它在大约5分钟后超时了,这仍然会是一个问题吗? - imatwork
1
没有超时时间可能会使您容易受到通过http向量的DOS攻击的威胁。 - Gershom Maes

12

是的,您提供的策略文件可以导致崩溃。

为了重复操作,只需向您的代码添加延迟:

net.createServer( function(socket) 
{
    for (i=0; i<1000000000; i++) ;
    socket.write("<?xml version=\"1.0\"?>\n");
…

使用telnet连接到端口。如果在延迟过期之前断开telnet连接,则在socket.write引发错误时会出现崩溃(未捕获的异常)。

为了避免这种情况,请在读/写socket之前添加错误处理程序:

net.createServer(function(socket)
{
    for(i=0; i<1000000000; i++);
    socket.on('error', function(error) { console.error("error", error); });
    socket.write("<?xml version=\"1.0\"?>\n");
}

如果你尝试上述的断开连接方式,你会得到一条日志信息而不是崩溃。

完成后记得移除延迟。


11

还有可能的一种情况(但很少见)是,如果您进行了服务器到服务器通信,并将 server.maxConnections 设置为非常低的值。

在 Node 的核心库 net.js 中,它将调用 clientHandle.close(),这也会导致错误 ECONNRESET:

if (self.maxConnections && self._connections >= self.maxConnections) {
  clientHandle.close(); // causes ECONNRESET on the other end
  return;
}

非常好的调用,但maxConnections默认值是Infinity。这种情况只会发生在您明确覆盖了该值的情况下(正如您所说)。 - Gajus

9

ECONNRESET是指当服务器端关闭TCP连接并且未能满足客户端的请求时发生的错误。服务器会响应一个消息,说明您引用了一个无效的连接。

为什么服务器会发送一个无效连接的请求?

假设您已经在客户端和服务器之间启用了一个保持活动状态的连接(keep-alive)。保持活动状态的超时时间设置为15秒。这意味着如果保持活动状态的时间超过15秒,它就会发送连接关闭请求。因此,在15秒后,服务器告诉客户端关闭连接。但是,当服务器发送这个请求时,客户端正在向服务器端发送一条新请求。由于这个连接现在是无效的,服务器将用ECONNRESET错误拒绝该请求。所以问题是由于对服务器端请求过少导致的。请禁用保持活动状态,这样问题就会得到解决。


这种情况会发生吗?我的意思是,客户端关闭了连接,但服务器仍然试图使用同一连接推送数据?在这种情况下,我们是否会在服务器端日志中得到相同的错误? - vikas kv
如何禁用Keep-Alive? - node_saini

8
我也遇到了这个错误,经过数天的调试和分析,我得以解决这个问题:
解决方法:
对于我来说,VirtualBox(用于Docker)是问题所在。 我在我的VM上配置了端口转发,并且仅在转发端口时发生错误。
总体结论:
以下观察结果可能会节省您我曾经花费的时间:
- 对于我来说,问题只出现在从本地主机到本地主机的连接上某个端口上。->检查更改任何这些常量是否解决了问题。 - 对于我来说,问题仅在我的计算机上出现。->让其他人尝试一下。 - 对于我来说,问题只在一段时间后才会发生,并且无法可靠地重现。 - 无法使用任何nodes或expresses(debug-)工具检查我的问题。->不要浪费时间在这个上面。 - ->找出是否有东西正在干扰您的网络(设置),例如虚拟机,防火墙等,这可能是问题的原因。

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