本作品采用知識共享署名 4.0 國際許可協議進行許可。轉載保留聲明頭部與原文鏈接https://luzeshu.com/blog/nodesource2
本博客同步在https://cnodejs.org/topic/56e3be21f5d830306e2f0fd3
本博客同步在http://www.cnblogs.com/papertree/p/5225201.html
2.1 項目代碼結構
node 主要的部分有4個【下圖最左列就是node項目源碼(4.2.2)的根目錄】:
1. 原生 js模塊:node提供給 用戶js 代碼的類接口,平時用的require('fs')、require('http')調用的都是這部分的代碼。【最左列的 lib文件夾,展開后是左二列】
2. node 源碼:node程序的main函數入口;還有提供給lib模塊的C++類接口。【最左列的 src 文件夾,展開后是第三列】
3. v8引擎:node用來解析、執行js代碼的運行環境。【最左列的deps文件夾展開后是第四列,v8和libuv等依賴都放在這里】
4. libuv:事件循環庫,提供最底層的 io操作接口(包括網絡io操作的epoll_wait()、文件異步io的線程池管理)、事件循環邏輯。 【第四列的uv文件夾,展開后是第五列】
記住這幾個路徑:
./lib
./src
./deps/uv
圖2-1-1
2.2 運行流程
下圖4個紅色序號分別對應着上篇博客提出的4個問題所在位置。后續博客分說。
接下來對一些關鍵地方進行說明:
1. 核心數據結構 default_loop_struct (結構體為struct uv_loop_s,后續祥講)
這個數據結構是事件循環的核心。當node執行到“加載js文件”這個步驟(結合下圖)時,用戶的js代碼如果有io操作:
那么js代碼通過調用 -》lib模塊(2.1 中的原生js模塊)-》C++模塊(2.1中的源碼部分) -》 libuv接口(2.1中deps/uv部分) -》最終的系統api,拿到系統返回的一個fd(文件描述符),和 js代碼傳進來的回調函數callback,封裝成一個io觀察者(一個uv__io_s類型的對象),保存到default_loop_struct;
2. 進入事件循環
當處理完 js代碼,如果有io操作,那么這時default_loop_struct是保存着對應的io觀察者的。
處理完js代碼,main函數繼續往下調用libuv的事件循環入口uv_run(),node進程進入事件循環:
uv_run()的while循環做的就是一件事,判斷default_loop_struct是否有存活的io觀察者。
a. 如果沒有io觀察者,那么uv_run()退出,node進程退出。
b. 而如果有io觀察者,那么uv_run()進入epoll_wait(),線程掛起等待,監聽對應的io觀察者是否有數據到來。有數據到來調用io觀察者里保存着的callback(js代碼),沒有數據到來時一直在epoll_wait()進行等待。
這里解答了博客(一)的“問題2”的一部分:為什么console.log()的js代碼導致node退出,而server.listen(80)導致線程掛起等待。
3. 這里一旦沒搞清邏輯就有個疑問:
第2點說在uv_run()里面如果監聽的io觀察者有數據到來,那么調用對應的callback,執行js代碼。如果沒有數據到來,一直在epoll_wait()等待。那如果我js代碼里面有新的 io操作想要交給epoll_wait()進行監聽,而此刻監聽着的io觀察者又沒有數據到來,線程一直在這里等待,那怎么辦?
首先第1點講到,執行js代碼的時候,通過調用node提供的C++接口最終把io觀察者都保存到default_loop_struct里面,js代碼執行完之后,node繼續運行才進入epoll_wait()等待。也就是說node在epoll_wait()的時候,js代碼執行完畢了。而js代碼的回調函數部分,本來的設定就是在epoll_wait()監聽的io觀察者被觸發之后才會執行回調,在epoll_wait()進行等待的時候,不可能存在“有新io操作要交給epoll_wait()去監聽”這樣的js代碼。
圖2-2-1