nodejs的错误处理过程记录

本文以连接错误ECONNREFUSED为例,看看nodejs对错误处理的过程。 假设我们有以下代码

1.  const net = require('net');
2.  net.connect({port: 9999})

如果本机上没有监听9999端口,那么我们会得到以下输出。

1.  events.js:170
2.        throw er; // Unhandled 'error' event
3.        ^
4.
5.  Error: connect ECONNREFUSED 127.0.0.1:9999
6.      at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1088:14)
7.  Emitted 'error' event at:
8.      at emitErrorNT (internal/streams/destroy.js:91:8)
9.      at emitErrorAndCloseNT (internal/streams/destroy.js:59:3)
10.     at processTicksAndRejections (internal/process/task_queues.js:81:17)

我们简单看一下connect的调用流程。

1.  const req = new TCPConnectWrap();
2.  req.oncomplete = afterConnect;
3.  req.address = address;
4.  req.port = port;
5.  req.localAddress = localAddress;
6.  req.localPort = localPort;
7.  // 开始三次握手建立连接
8.  err = self._handle.connect(req, address, port);

接着我们看一下C++层connect的逻辑

1.  err = req_wrap->Dispatch(uv_tcp_connect,
2.                               &wrap->handle_,
3.                               reinterpret_cast<const sockaddr*>(&addr),
4.                               AfterConnect);

C++层直接调用Libuv的uv_tcp_connect,并且设置回调是AfterConnect。接着我们看libuv的实现。

1.  do {
2.      errno = 0;
3.      // 非阻塞调用
4.      r = connect(uv__stream_fd(handle), addr, addrlen);
5.    } while (r == -1 && errno == EINTR);
6.    // 连接错误,判断错误码
7.    if (r == -1 && errno != 0) {
8.      // 还在连接中,不是错误,等待连接完成,事件变成可读
9.      if (errno == EINPROGRESS)
10.       ; /* not an error */
11.     else if (errno == ECONNREFUSED)
12.       // 连接被拒绝
13.       handle->delayed_error = UV__ERR(ECONNREFUSED);
14.     else
15.       return UV__ERR(errno);
16.   }
17.   uv__req_init(handle->loop, req, UV_CONNECT);
18.   req->cb = cb;
19.   req->handle = (uv_stream_t*) handle;
20.   QUEUE_INIT(&req->queue);
21.   // 挂载到handle,等待可写事件
22.   handle->connect_req = req;
23.   uv__io_start(handle->loop, &handle->io_watcher, POLLOUT);

我们看到Libuv以异步的方式调用操作系统,然后把request挂载到handle中,并且注册等待可写事件,当连接失败的时候,就会执行uv stream_io回调,我们看一下Libuv的处理(uv stream_io)。

1.  getsockopt(uv__stream_fd(stream),
2.                 SOL_SOCKET,
3.                 SO_ERROR,
4.                 &error,
5.                 &errorsize);
6.  error = UV__ERR(error);
7.  if (req->cb)
8.      req->cb(req, error);

获取错误信息后回调C++层的AfterConnect。

1.  Local<Value> argv[5] = {
2.     Integer::New(env->isolate(), status),
3.     wrap->object(),
4.     req_wrap->object(),
5.     Boolean::New(env->isolate(), readable),
6.     Boolean::New(env->isolate(), writable)
7.   };
8.
9.   req_wrap->MakeCallback(env->oncomplete_string(), arraysize(argv), argv);

接着调用JS层的oncomplete回调。

1.  const ex = exceptionWithHostPort(status,
2.                                   'connect',
3.                                   req.address,
4.                                   req.port,
5.                                   details);
6.  if (details) {
7.    ex.localAddress = req.localAddress;
8.    ex.localPort = req.localPort;
9.  }
10. // 销毁socket
11. self.destroy(ex);

exceptionWithHostPort构造错误信息,然后销毁socket并且以ex为参数触发error事件。我们看看uvExceptionWithHostPort的实现。

1.  function uvExceptionWithHostPort(err, syscall, address, port) {
2.    const [ code, uvmsg ] = uvErrmapGet(err) || uvUnmappedError;
3.    const message = `${syscall} $[code]: ${uvmsg}`;
4.    let details = '';
5.
6.    if (port && port > 0) {
7.      details = ` ${address}:${port}`;
8.    } else if (address) {
9.      details = ` ${address}`;
10.   }
11.   const tmpLimit = Error.stackTraceLimit;
12.   Error.stackTraceLimit = 0;
13.   const ex = new Error(`${message}${details}`);
14.   Error.stackTraceLimit = tmpLimit;
15.   ex.code = code;
16.   ex.errno = err;
17.   ex.syscall = syscall;
18.   ex.address = address;
19.   if (port) {
20.     ex.port = port;
21.   }
22.   // 获取调用栈信息但不包括当前调用的函数uvExceptionWithHostPort,注入stack字段到ex中
23.   Error.captureStackTrace(ex, excludedStackFn || uvExceptionWithHostPort);
24.   return ex;
25. }

我们看到错误信息主要通过uvErrmapGet获取

1.  function uvErrmapGet(name) {
2.    uvBinding = lazyUv();
3.    if (!uvBinding.errmap) {
4.      uvBinding.errmap = uvBinding.getErrorMap();
5.    }
6.    return uvBinding.errmap.get(name);
7.  }
8.
9.  function lazyUv() {
10.   if (!uvBinding) {
11.     uvBinding = internalBinding('uv');
12.   }
13.   return uvBinding;
14. }

继续往下看,uvErrmapGet调用了C++层的uv模块的getErrorMap。

1.  void GetErrMap(const FunctionCallbackInfo<Value>& args) {
2.    Environment* env = Environment::GetCurrent(args);
3.    Isolate* isolate = env->isolate();
4.    Local<Context> context = env->context();
5.
6.    Local<Map> err_map = Map::New(isolate);
7.    // 从per_process::uv_errors_map中获取错误信息
8.    size_t errors_len = arraysize(per_process::uv_errors_map);
9.    // 赋值
10.   for (size_t i = 0; i < errors_len; ++i) {
11.      // map的键是 uv_errors_map每个元素中的value,值是name和message
12.     const auto& error = per_process::uv_errors_map[i];
13.     Local<Value> arr[] = {OneByteString(isolate, error.name),
14.                           OneByteString(isolate, error.message)};
15.     if (err_map
16.             ->Set(context,
17.                   Integer::New(isolate, error.value),
18.                   Array::New(isolate, arr, arraysize(arr)))
19.             .IsEmpty()) {
20.       return;
21.     }
22.   }
23.
24.   args.GetReturnValue().Set(err_map);
25. }

我们看到错误信息存在per_process::uv_errors_map中,我们看一下uv_errors_map的定义。

1.  struct UVError {
2.    int value;
3.    const char* name;
4.    const char* message;
5.  };
6.
7.  static const struct UVError uv_errors_map[] = {
8.  #define V(name, message) {UV_##name, #name, message},
9.      UV_ERRNO_MAP(V)
10. #undef V
11. };

UV_ERRNO_MAP宏展开后如下

1.  {UV_E2BIG, "E2BIG", "argument list too long"},
2.  {UV_EACCES, "EACCES", "permission denied"},
3.  {UV_EADDRINUSE, "EADDRINUSE", "address already in use"},
4.  ……

所以导出到JS层的结果如下

1.  {
2.    // 键是一个数字,由Libuv定义,其实是封装了操作系统的定义
3.    UV_ECONNREFUSED: ["ECONNREFUSED", "connection refused"],
4.    UV_ECONNRESET: ["ECONNRESET", "connection reset by peer"]
5.    ...
6.  }

Node.js最后会组装这些信息返回给调用方。这就是我们输出的错误信息。那么为什么会是ECONNREFUSED呢?我们看一下操作系统对于该错误码的逻辑。

1.  static void tcp_reset(struct sock *sk)
2.  {
3.      switch (sk->sk_state) {
4.          case TCP_SYN_SENT:
5.              sk->sk_err = ECONNREFUSED;
6.              break;
7.           // ...
8.      }
9.
10. }

当操作系统收到一个发给该socket的rst包的时候会执行tcp_reset,我们看到当socket处于发送syn包等待ack的时候,如果收到一个fin包,则会设置错误码为ECONNREFUSED。我们输出的正是这个错误码。

总结

到此这篇关于nodejs的错误处理过程记录的文章就介绍到这了,更多相关nodejs错误处理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Node错误处理笔记之挖坑系列教程

    前言 前段时间要在项目中加上日志的上报,顺势了解了下怎么在node中完善错误信息的收集.下面话不多说了,来一起看看详细的介绍吧 01 Error JS 中的 Error 对象,包含了错误的具体信息,包括 name.message.错误堆栈 stack 等.可以以 new Error 方式创建实例抛出,或调用 Error.captureStackTrace 为已有对象添加 stack 错误堆栈信息 而后抛出. 02 错误抛出几种方式 * Throw*:Javascript 抛出的异常,是以 thr

  • NodeJS处理Express中异步错误

    摘要 比起回调函数,使用 Promise 来处理异步错误要显得优雅许多. 结合 Express 内置的错误处理机制和 Promise 极大地降低产生未捕获错误(uncaught exception)的可能性. Promise 在ES6中是默认选项.如果使用 Babel 转译,它也可以与 Generators 或者 Async/Await 相结合. 本文主要阐述如何在 Express 中使用错误处理中间件(error-handling middleware)来高效处理异步错误.在 Github 上

  • node错误处理与日志记录的实现

    node项目中的错误处理 node中Error对象的使用 使用captureStackTrace方法加入自带的错误信息 // Error对象自带的属性 Error.captureStackTrace // 如何使用captureStackTrace var obj = { message: 'something is wrong' } Error.captureStackTrace(obj) throw obj // 此时会抛出obj对象的message内信息 使用try catch捕获错误 直

  • 如何优雅地在Node应用中进行错误异常处理

    不知道你有没有遇到这样一种情况,某天你写的代码在线上突然发生错误,然后你打开控制台,却对着打过包的错误信息毫无头绪?又或者说是代码在node端出现了问题,你查看错误日志的时候,却发现日志文件中都是杂乱的错误堆栈信息. 其实上面这些问题都可以通过在代码中引入合适的错误机制进行解决.大部分时候,由于程序员在开发过程中更加关注需求的实现,反而会忽视一些底层的工作.而错误处理机制就相当于我们代码上的最后一道保险,在程序发生已知或者意外的问题的时候,可以让开发者在第一时间获取信息,从而快速定位并解决问题.

  • nodejs的错误处理过程记录

    本文以连接错误ECONNREFUSED为例,看看nodejs对错误处理的过程. 假设我们有以下代码 1. const net = require('net'); 2. net.connect({port: 9999}) 如果本机上没有监听9999端口,那么我们会得到以下输出. 1. events.js:170 2. throw er; // Unhandled 'error' event 3. ^ 4. 5. Error: connect ECONNREFUSED 127.0.0.1:9999

  • 一次docker错误的耗时排查过程记录

    由来 客户是深信服的订制系统,基于 centos 改的,排查半天发现居然是文件损坏,而不是 docker 的问题. 环境信息 docker信息: $ docker info Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 2 Server Version: 18.09.3 Storage Driver: overlay2 Backing Filesystem: xfs Supports d_type: true Native Overl

  • 安装nodejs和yarn及配置淘宝源过程记录

    目录 1.下载nodejs 2.双击安装 3.重置全局npm源,修正为 淘宝的 NPM 镜像: 4.安装Yarn 5.设置Yarn的淘宝源 1.下载nodejs 访问下载 | Node.js 中文网 (nodejs.cn) 本站下载地址:点击下载 2.双击安装 全部都点Next,最后Install即可 3.重置全局npm源,修正为 淘宝的 NPM 镜像: 按住[Shift]键,点击鼠标右键->在此次打开PowerShell窗口 执行下面的指令 npm install -g cnpm --regi

  • springboot使用logback文件查看错误日志过程详解

    这篇文章主要介绍了springboot使用logback文件查看错误日志过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 <?xml version="1.0" encoding="UTF-8"?> <!-- 从高到地低 OFF . FATAL . ERROR . WARN . INFO . DEBUG . TRACE . ALL --> <!-- 日志输出规则 根据当前ROOT

  • vue中使用protobuf的过程记录

    由于目前公司采用了ProtoBuf做前后端数据交互,进公司以来一直用的是公司大神写好的基础库,完全不了解底层是如何解析的,一旦报错只能求人,作为一只还算有钻研精神的猿,应该去了解一下底层的实现,在这里记录一下学习过程. Protobuf简单介绍 Google Protocol Buffer(简称 Protobuf)是一种轻便高效的结构化数据存储格式,平台无关.语言无关.可扩展,可用于通讯协议和数据存储等领域. 有几个优点: 1.平台无关,语言无关,可扩展: 2.提供了友好的动态库,使用简单; 3

  • ubuntu20.04 安装 MySQL5.7过程记录

    目录 背景介绍# 安装过程# 笔者的环境: 添加 MySQL 源 安装 MySQL 背景介绍# ubuntu 20.04 版本系统自带的 MySQL 版本是 8.0,普通方法很难安装 5.7 版本的.由于 8.0 版本较 5.7 版本做了不少改动,笔者比较习惯使用 5.7 版本.网上搜做了一圈,跟着各种教程试了很多遍,最后终于找到了成功的方法.过程记录分享出来,供大家参考. 安装过程# 笔者的环境: root@hz192-168-1.55:/home# cat /etc/lsb-release

  • 从axios源码角度解决bug的过程记录

    目录 现象 排查思路 1. 引入 vConsole 在移动端调试 2. 从大范围到小范围的 log 3. axios 源码一览 排查角度 - interceptor 排查角度 - xhr 4. 解决问题 现象 公司的一个 H5 站点在头条 App 里白屏,在手百.QQ 浏览器.Safari.Chrome 等都正常 排查思路 1. 引入 vConsole 在移动端调试 因为移动端没有 PC 里那样方便的调试工具可以清晰的查看 log 和 network 之类有用的信息,只能借助 vConsole.

  • Ubuntu 16.04安装微信的过程记录

    微信没有出Linux的版本,但是可以通过以下方式解决: 1.使用网页版,除了没有公众号之后,一切都没问题,包括传文件等. 网页登录地址:https://wx.qq.com/ 2.使用第三方版本,只不过这个是桌面应用,原理是通过网页API集成的,稳定性还是可以的. 网站:https://github.com/geeeeeeeeek/electronic-wechat/releases,离线版本:(链接: https://pan.baidu.com/s/1i5Dr15r 密码: rc93) 其实不用

  • Springboot2.0自适应效果错误响应过程解析

    这篇文章主要介绍了Springboot2.0自适应效果错误响应过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 实现效果当访问thymeleaf渲染页面时,显示的是自定义的错误页面 当以接口方式访问时,显示的是自定义的json数据响应 1. 编写自定义异常 package cn.jfjb.crud.exception; /** * @author john * @date 2019/11/24 - 9:48 */ public class

  • 通过SSH连接本地linux虚拟机的过程记录

    实验环境: 物理机 Windows 10 x64 物理网卡信息 IPv4地址:192.168.123.205 子网掩码:255.255.255.0 默认网关:192.168.123.1 虚拟机VMware Workstation14 Cent OS 6.7 x64 VMnet8信息: IPv4地址:192.168.21.0 子网掩码:255.255.255.0 默认网关:192.168.21.2 一.修改VMware的网络设置 位置:工具栏->编辑->虚拟网络编辑器[管理员] NAT设置-&g

随机推荐