您现在的位置是:亿华云 > 人工智能
聊聊Nodejs的错误处理
亿华云2025-10-09 01:31:32【人工智能】8人已围观
简介本文转载自微信公众号「编程杂技」,作者theanarkh。转载本文请联系编程杂技公众号。本文以连接错误ECONNREFUSED为例,看看nodejs对错误处理的过程。假设我们有以下代码constnet
本文转载自微信公众号「编程杂技」,聊聊理作者theanarkh。误处转载本文请联系编程杂技公众号。聊聊理
本文以连接错误ECONNREFUSED为例,误处看看nodejs对错误处理的聊聊理过程。
假设我们有以下代码
const net = require(net); net.connect({ port: 9999})如果本机上没有监听9999端口,误处那么我们会得到以下输出。聊聊理
events.js:170 throw er; // Unhandled error event ^ Error: connect ECONNREFUSED 127.0.0.1:9999 at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1088:14) Emitted error event at: at emitErrorNT (internal/streams/destroy.js:91:8) at emitErrorAndCloseNT (internal/streams/destroy.js:59:3) at processTicksAndRejections (internal/process/task_queues.js:81:17)我们简单看一下connect的误处调用流程。
const req = new TCPConnectWrap(); req.oncomplete = afterConnect; req.address = address; req.port = port; req.localAddress = localAddress; req.localPort = localPort; // 开始三次握手建立连接 err = self._handle.connect(req,聊聊理 address, port);接着我们看一下C++层connect的逻辑
err = req_wrap->Dispatch(uv_tcp_connect, &wrap->handle_, reinterpret_cast(&addr), AfterConnect);C++层直接调用Libuv的uv_tcp_connect,并且设置回调是误处AfterConnect。接着我们看libuv的聊聊理实现。
do { errno = 0; // 非阻塞调用 r = connect(uv__stream_fd(handle),误处 addr, addrlen); } while (r == -1 && errno == EINTR); // 连接错误,判断错误码 if (r == -1 && errno != 0) { // 还在连接中,聊聊理不是误处错误,等待连接完成,聊聊理事件变成可读 if (errno == EINPROGRESS) ; /* not an error */ else if (errno == ECONNREFUSED) // 连接被拒绝 handle->delayed_error = UV__ERR(ECONNREFUSED); else return UV__ERR(errno); } uv__req_init(handle->loop, req, UV_CONNECT); req->cb = cb; req->handle = (uv_stream_t*) handle; QUEUE_INIT(&req->queue); // 挂载到handle,等待可写事件 handle->connect_req = req; uv__io_start(handle->loop, &handle->io_watcher, POLLOUT);我们看到Libuv以异步的方式调用操作系统,然后把request挂载到handle中,并且注册等待可写事件,云服务器提供商当连接失败的时候,就会执行uv__stream_io回调,我们看一下Libuv的处理(uv__stream_io)。
getsockopt(uv__stream_fd(stream), SOL_SOCKET, SO_ERROR, &error, &errorsize); error = UV__ERR(error); if (req->cb) req->cb(req, error);获取错误信息后回调C++层的AfterConnect。
Localargv[5] = { Integer::New(env->isolate(), status), wrap->object(), req_wrap->object(), Boolean::New(env->isolate(), readable), Boolean::New(env->isolate(), writable) }; req_wrap->MakeCallback(env->oncomplete_string(), arraysize(argv), argv);接着调用JS层的oncomplete回调。
const ex = exceptionWithHostPort(status, connect, req.address, req.port, details); if (details) { ex.localAddress = req.localAddress; ex.localPort = req.localPort; } // 销毁socket self.destroy(ex);exceptionWithHostPort构造错误信息,然后销毁socket并且以ex为参数触发error事件。我们看看uvExceptionWithHostPort的实现。
function uvExceptionWithHostPort(err, syscall, address, port) { const [ code, uvmsg ] = uvErrmapGet(err) || uvUnmappedError; const message = `${ syscall} ${ code}: ${ uvmsg}`; let details = ; if (port && port > 0) { details = ` ${ address}:${ port}`; } else if (address) { details = ` ${ address}`; } const tmpLimit = Error.stackTraceLimit; Error.stackTraceLimit = 0; const ex = new Error(`${ message}${ details}`); Error.stackTraceLimit = tmpLimit; ex.code = code; ex.errno = err; ex.syscall = syscall; ex.address = address; if (port) { ex.port = port; } // 获取调用栈信息但不包括当前调用的函数uvExceptionWithHostPort,注入stack字段到ex中 Error.captureStackTrace(ex, excludedStackFn || uvExceptionWithHostPort); return ex; }我们看到错误信息主要通过uvErrmapGet获取
unction uvErrmapGet(name) { uvBinding = lazyUv(); if (!uvBinding.errmap) { uvBinding.errmap = uvBinding.getErrorMap(); } return uvBinding.errmap.get(name); } function lazyUv() { if (!uvBinding) { uvBinding = internalBinding(uv); } return uvBinding; }继续往下看,uvErrmapGet调用了C++层的uv模块的getErrorMap。
void GetErrMap(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); Localcontext = env->context(); Local // 从per_process::uv_errors_map中获取错误信息 size_t errors_len = arraysize(per_process::uv_errors_map); // 赋值 for (size_t i = 0; i < errors_len; ++i) { // map的键是 uv_errors_map每个元素中的value,值是name和message const auto& error = per_process::uv_errors_map[i]; Localarr[] = { OneByteString(isolate, error.name), OneByteString(isolate, error.message)}; if (err_map ->Set(context, Integer::New(isolate, error.value), Array::New(isolate, arr, arraysize(arr))) .IsEmpty()) { return; } } args.GetReturnValue().Set(err_map); }我们看到错误信息存在per_process::uv_errors_map中,我们看一下uv_errors_map的云南idc服务商定义。
struct UVError { int value; const char* name; const char* message; }; static const struct UVError uv_errors_map[] = { #define V(name, message) { UV_##name, #name, message}, UV_ERRNO_MAP(V) #undef V };UV_ERRNO_MAP宏展开后如下
{ UV_E2BIG, "E2BIG", "argument list too long"}, { UV_EACCES, "EACCES", "permission denied"}, { UV_EADDRINUSE, "EADDRINUSE", "address already in use"}, ……所以导出到JS层的结果如下
{ // 键是一个数字,由Libuv定义,其实是封装了操作系统的定义 UV_ECONNREFUSED: ["ECONNREFUSED", "connection refused"], UV_ECONNRESET: ["ECONNRESET", "connection reset by peer"] ... }Node.js最后会组装这些信息返回给调用方。这就是我们输出的错误信息。那么为什么会是ECONNREFUSED呢?我们看一下操作系统对于该错误码的逻辑。
static void tcp_reset(struct sock *sk) { switch (sk->sk_state) { case TCP_SYN_SENT: sk->sk_err = ECONNREFUSED; break; // ... } }当操作系统收到一个发给该socket的rst包的时候会执行tcp_reset,我们看到当socket处于发送syn包等待ack的时候,如果收到一个fin包,则会设置错误码为ECONNREFUSED。我们输出的正是这个错误码。
很赞哦!(5)
相关文章
- 2、根据用户基础选择访问提供程序。由于互联问题的存在,接入商的选择也非常重要,如果用户群主要在联通,尽量选择联通接入较好的接入商,如果用户群主要在电信,那么选择电信接入较好的接入商。如果用户组位于国家/地区,则选择更好的访问提供程序进行交互。
- GitHub超9千星:一个API调用六种架构,27个预训练模型
- 高速飞机上换引擎,MQ如何实现平滑迁移?
- 不改代码也能全面Serverless化,阿里中间件如何破解这一难题?
- 3、商标域名一经注册,就可以作为域名裁决过程中的主要信息之一。这可以大大增加公司被抢注的相关域名胜诉的机会。
- 一文搞懂让你懵圈的超级计算机:真的不是堆CPU就行
- 信号量限流,高并发场景不得不说的秘密
- 使用Python解析参数
- 新手可以注册cc域名吗?cc域名有什么特点?
- Jetbrains发布2019开发者生态报告:Java主流,Go有前途