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。我们输出的正是这个错误码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值