nodejs的錯誤處理過程記錄
本文以連接錯誤ECONNREFUSED為例,看看nodejs對錯誤處理的過程。 假設(shè)我們有以下代碼
1. const net = require('net'); 2. net.connect({port: 9999})
如果本機(jī)上沒有監(jiān)聽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的調(diào)用流程。
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++層直接調(diào)用Libuv的uv_tcp_connect,并且設(shè)置回調(diào)是AfterConnect。接著我們看libuv的實(shí)現(xiàn)。
1. do { 2. errno = 0; 3. // 非阻塞調(diào)用 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以異步的方式調(diào)用操作系統(tǒng),然后把request掛載到handle中,并且注冊等待可寫事件,當(dāng)連接失敗的時候,就會執(zhí)行uv stream_io回調(diào),我們看一下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);
獲取錯誤信息后回調(diào)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);
接著調(diào)用JS層的oncomplete回調(diào)。
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構(gòu)造錯誤信息,然后銷毀socket并且以ex為參數(shù)觸發(fā)error事件。我們看看uvExceptionWithHostPort的實(shí)現(xiàn)。
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. // 獲取調(diào)用棧信息但不包括當(dāng)前調(diào)用的函數(shù)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. }
繼續(xù)往下看,uvErrmapGet調(diào)用了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. ……
所以導(dǎo)出到JS層的結(jié)果如下
1. { 2. // 鍵是一個數(shù)字,由Libuv定義,其實(shí)是封裝了操作系統(tǒng)的定義 3. UV_ECONNREFUSED: ["ECONNREFUSED", "connection refused"], 4. UV_ECONNRESET: ["ECONNRESET", "connection reset by peer"] 5. ... 6. }
Node.js最后會組裝這些信息返回給調(diào)用方。這就是我們輸出的錯誤信息。那么為什么會是ECONNREFUSED呢?我們看一下操作系統(tǒng)對于該錯誤碼的邏輯。
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. }
當(dāng)操作系統(tǒng)收到一個發(fā)給該socket的rst包的時候會執(zhí)行tcp_reset,我們看到當(dāng)socket處于發(fā)送syn包等待ack的時候,如果收到一個fin包,則會設(shè)置錯誤碼為ECONNREFUSED。我們輸出的正是這個錯誤碼。
總結(jié)
到此這篇關(guān)于nodejs的錯誤處理過程記錄的文章就介紹到這了,更多相關(guān)nodejs錯誤處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于node搭建服務(wù)器,寫接口,調(diào)接口,跨域的實(shí)例
今天小編就為大家分享一篇基于node搭建服務(wù)器,寫接口,調(diào)接口,跨域的實(shí)例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-05-05nodejs文件操作模塊FS(File System)常用函數(shù)簡明總結(jié)
這篇文章主要介紹了nodejs文件操作模塊FS(File System)常用函數(shù)簡明總結(jié),對FS模塊的大部份異步函數(shù)做了介紹,而且用中文注釋,這下用起來方便了,需要的朋友可以參考下2014-06-06nestjs搭建HTTP與WebSocket服務(wù)詳細(xì)過程
這篇文章主要介紹了nestjs搭建HTTP與WebSocket服務(wù)詳細(xì)過程的相關(guān)資料,需要的朋友可以參考下2022-11-11Node.js實(shí)現(xiàn)簡單的爬取的示例代碼
這篇文章主要介紹了Node.js實(shí)現(xiàn)簡單的爬取的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06node.js調(diào)用C++函數(shù)的方法示例
這篇文章主要介紹了node.js調(diào)用C++函數(shù)的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-09-09node.js中fs文件系統(tǒng)目錄操作與文件信息操作
本篇文章給大家詳細(xì)分析了node.js中fs文件系統(tǒng)目錄操作與文件信息操作的方法以及代碼詳解,需要的讀者可以參考下。2018-02-02Node.js HTTP服務(wù)器中的文件、圖片上傳的方法
這篇文章主要介紹了Node.js HTTP服務(wù)器中的文件、圖片上傳的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09