成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

干貨剖析 | 走進(jìn)Node.js之啟動(dòng)過程

Simon / 2372人閱讀

摘要:具體調(diào)用鏈路如圖函數(shù)主要是解析啟動(dòng)參數(shù),并過濾選項(xiàng)傳給引擎。查閱文檔之后發(fā)現(xiàn),通過指定參數(shù)可以設(shè)置線程池大小。原來的字節(jié)碼編譯優(yōu)化還有都是通過多線程完成又繼續(xù)深入調(diào)查,發(fā)現(xiàn)環(huán)境變量會影響的線程池大小。執(zhí)行過程如下調(diào)用執(zhí)行。

作者:正龍 (滬江Web前端開發(fā)工程師)
本文原創(chuàng),轉(zhuǎn)載請注明作者及出處。

隨著Node.js的普及,越來越多的開發(fā)者使用Node.js來搭建環(huán)境,也有很多公司開始把Web站點(diǎn)遷移到Node.js服務(wù)器。Node.js的優(yōu)勢顯而易見,本文不再贅述,那么它是如何做到的呢?內(nèi)部的邏輯又是什么?帶著這些問題,筆者開始了研究Node.js的漫漫長征路。今天,筆者將跟大家探討一下Node.js的啟動(dòng)原理。

Node.js內(nèi)部主要依賴Google的V8引擎和libuv實(shí)現(xiàn)。V8,想必大家會比較熟悉,它首創(chuàng)把JavaScript直接翻譯成匯編代碼的方式執(zhí)行,讓很多不可能變成了可能,例如Node.js。libuv,是一個(gè)跨平臺的異步IO庫,它所說的IO除了包含本地文件操作,還包含TCP、UDP等網(wǎng)絡(luò)套接字操作,范圍甚至可以擴(kuò)展到所有流操作(Stream)。所以,我們可以把Node.js理解為添加了網(wǎng)絡(luò)功能的V8。

為了描述方便,下面提到的環(huán)境是基于Windows 7專業(yè)版。用MAC的伙伴們也不用慌,內(nèi)容實(shí)質(zhì)仍然適用,可能具體名詞有些區(qū)別。另外,伙伴們可以下載一份Node.js的源代碼(點(diǎn)此下載),本文用的是6.10.0 LTS。

我們打開Node.js的二進(jìn)制發(fā)布包,里面內(nèi)容很簡單:node.exe、npm和node.h頭文件。node.h頭文件只有開發(fā)Node.js插件時(shí)才會用到。當(dāng)我們啟動(dòng)node.exe時(shí),它到底做了哪些事情?

首先,它是一個(gè)EXE可執(zhí)行文件,那肯定會有一個(gè)main函數(shù)。Node.js的main函數(shù)定義在node_main.cc中,它主要是初始化V8 Platform和v8引擎;然后會啟動(dòng)一個(gè)Node.js實(shí)例。具體調(diào)用鏈路如圖:

Init函數(shù)主要是解析Node.js啟動(dòng)參數(shù),并過濾V8選項(xiàng)傳給JavaScript引擎。

Node.js的main函數(shù)原來這么短,那它應(yīng)該很快運(yùn)行完并返回。實(shí)際上,命令行窗口會一直等待著,并沒有馬上退出,這又是怎么回事呢?答案就在StartInstance里。首先它會創(chuàng)建V8執(zhí)行沙盒,生成并初始化Node.js運(yùn)行環(huán)境對象,然后啟動(dòng)Node.js的循環(huán)等待。具體如圖:

也就是說Node.js的主線程主要消費(fèi)來自UV默認(rèn)事件循環(huán)(uv_default_loop)和V8的MainThreadQueue和MainThreadDelayedQueue的任務(wù)。uv_run是一個(gè)阻塞調(diào)用。如果隊(duì)列中有任務(wù),則執(zhí)行并返回true,如果沒有的話,會阻塞住當(dāng)前線程;如果返回false,則整個(gè)Node.js進(jìn)程會釋放資源并退出。注意參數(shù)UV_RUN_ONCE,意思是從隊(duì)列中只取一個(gè)任務(wù)執(zhí)行,不管隊(duì)列中當(dāng)前是否有多個(gè)任務(wù)。

到這兒,大概可以理解到Node.js的“單線程”是怎么回事。那運(yùn)行的Node.js進(jìn)程確實(shí)只開啟了一個(gè)線程嗎?我們打開任務(wù)管理器看看:

實(shí)際上,Node.js進(jìn)程當(dāng)前有7個(gè)線程。查閱文檔之后發(fā)現(xiàn),Node.js通過指定參數(shù)--v8-pool-size可以設(shè)置V8線程池大小。原來V8的字節(jié)碼編譯、優(yōu)化還有GC都是通過多線程完成;又繼續(xù)深入調(diào)查,發(fā)現(xiàn)環(huán)境變量UV_THREADPOOL_SIZE會影響libuv的線程池大小。

Node.js目前為止做的事情可以歸納為,初始化V8和libuv。接下來,我們看看Node.js自身運(yùn)行環(huán)境是怎樣構(gòu)建起來的。Node.js自身的運(yùn)行環(huán)境由Environment類表示,我們需要把process對象構(gòu)建起來。process對象在JavaScript應(yīng)用代碼中是可以訪問到,它的文檔可以狠戳這兒。注意,process現(xiàn)在還沒有賦值給Global對象。CreateEnvironment執(zhí)行流程如圖:

調(diào)用setAutorunMicrotask禁止V8自己消費(fèi)隊(duì)列中的任務(wù)。SetupProcessObject主要設(shè)置process的屬性,例如比較重要的binding,還有其它提供給開發(fā)者的字段,比如cpuUsage、hrtime、uptime等。binding用于獲取C/C++構(gòu)建的模塊,Node.js中的net庫就是通過這種方式最終調(diào)用到libuv。

binding就是做模塊查找,其執(zhí)行過程如下:

從Args中獲取到模塊名稱。

從Binding Cache中看是否能找到模塊,如果有直接返回模塊的exports。

3往Module Load List中追加一條模塊記錄,名稱為"binding " + 模塊名。

調(diào)用get_builtin_module,參數(shù)是模塊名,get_builtin_module會從modlist_builtin列表中查找內(nèi)置模塊,所有內(nèi)置模塊和第三方擴(kuò)展都記錄在modlist_builtin列表中。C/C++模塊通過NODE_MODULE_CONTEXT_AWARE_BUILTIN注冊,第三方擴(kuò)展模塊通過NODE_MODULE注冊。最終都會調(diào)用node_module_register。node_module結(jié)構(gòu)體包含注冊函數(shù)、模塊名稱、文件名稱等信息。

如果查找到,則返回對應(yīng)模塊的exports。

如果模塊名是constants,則調(diào)用DefineContstants。

如果模塊名是natives,則調(diào)用DefineJavaScript,會返回所有內(nèi)置模塊,它們一般由Javascript實(shí)現(xiàn)。這些模塊在/lib目錄下,會通過js2c.py轉(zhuǎn)成c代碼,js2c.py會生成一個(gè)臨時(shí)文件node_natives.h,里面包含了NODE_NATIVES_MAP的定義。

否則,拋出錯(cuò)誤:沒有指定名稱的模塊。

環(huán)境對象準(zhǔn)備好之后,就開始真正加載Node.js自身提供的JavaScript類庫代碼。LoadEnvironment執(zhí)行過程如下:

調(diào)用ExecuteString執(zhí)行bootstrap_node.js。bootstrap_node.js文件里定義了一個(gè)函數(shù)它會往Global對象上添加屬性,通過internal/module加載Node.js自身提供的JavaScript類庫。

執(zhí)行上一步返回的函數(shù),并傳入env->process_object()對象。

到這兒,我們可以總結(jié)2個(gè)問題:

Node.js里面自己提供的JavaScript庫是怎么實(shí)現(xiàn)的?

通過C/C++代碼封裝成Node.js內(nèi)置模塊,然后再通過process.binding暴露給JavaScript。

JavaScript庫文件是怎么打包在node.exe中?

Node.js內(nèi)置的JavaScript文件,通過js2c.py編譯生成臨時(shí)文件node_natives.h。

原理思路基本搞明白之后,下面我們來做個(gè)小實(shí)例:如何把C++對象暴露給JavaScript。
程序主要是C++和JavaScript的交互,通過Node.js插件的方式運(yùn)行。所以大家需要先了解下如何編譯Node.js插件,官方文檔猛戳這兒。

首先定義要導(dǎo)出的C++類,構(gòu)造器可以傳入一個(gè)數(shù)值;調(diào)用成員方法PlusOne,數(shù)值自增1并返回當(dāng)前值。

namespace demo {
    class MyObject : public node::ObjectWrap {
    public:
        static void Init(v8::Local exports);
        static void NewInstance(const v8::FunctionCallbackInfo& args);
        inline double value() const { return _value; }

    private:
        explicit MyObject(double value = 0);
        ~MyObject();

        static void New(const v8::FunctionCallbackInfo& args);
        static void PlusOne(const v8::FunctionCallbackInfo& args);
        static v8::Persistent constructor;
        double _value;
    };
}

實(shí)現(xiàn)文件

    void MyObject::NewInstance(const FunctionCallbackInfo& args) {
        Isolate* isolate = args.GetIsolate();

        const unsigned argc = 1;
        Local argv[argc] = { args[0] };
        Local cons = Local::New(isolate, constructor);
        Local context = isolate->GetCurrentContext();
        Local instance = cons->NewInstance(context, argc, argv).ToLocalChecked();

        args.GetReturnValue().Set(instance);
    }


    void MyObject::Init(Local exports) {
        Isolate* isolate = exports->GetIsolate();

        Local tpl = FunctionTemplate::New(isolate, New);
        tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));
        tpl->InstanceTemplate()->SetInternalFieldCount(1);

        NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);

        constructor.Reset(isolate, tpl->GetFunction());
        exports->Set(String::NewFromUtf8(isolate, "MyObject"), tpl->GetFunction());
    }

    void MyObject::New(const FunctionCallbackInfo& args) {
        double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
        MyObject* obj = new MyObject(value);
        obj->Wrap(args.This());
        args.GetReturnValue().Set(args.This());
    }

    void MyObject::PlusOne(const FunctionCallbackInfo& args) {
        Isolate* isolate = args.GetIsolate();
        MyObject* obj = ObjectWrap::Unwrap(args.Holder());
        obj->_value += 1;

        args.GetReturnValue().Set(Number::New(isolate, obj->_value));
    }

    NODE_MODULE(addon, MyObject::Init)

修改binding.gyp文件

{
  "targets": [
    {
      "target_name": "addon",
      "sources": [ "myobject.cc" ]
    }
  ]
}

通過node-gyp build編譯成功之后會在build/Release/目錄下生成文件addon.node。這樣我們就可以在JavaScript中使用MyObject了:

const addon = require("./addon");

let obj = new addon.MyObject();
console.log(obj.plusOne());
console.log(obj.plusOne());
console.log(obj.plusOne());

let obj1 = new addon.MyObject(10);
console.log(obj1.plusOne());

執(zhí)行結(jié)果如下:

雖然Node.js的啟動(dòng)過程很簡潔,但還是有一些問題可以繼續(xù)深挖。比如,一個(gè)網(wǎng)絡(luò)請求在Node.js中到底是怎么被處理的呢?希望本文可以拋磚引玉,在入門階段給大家一點(diǎn)幫助。

iKcamp原創(chuàng)新書《移動(dòng)Web前端高效開發(fā)實(shí)戰(zhàn)》已在亞馬遜、京東、當(dāng)當(dāng)開售。

>> 滬江Web前端上海團(tuán)隊(duì)招聘【W(wǎng)eb前端架構(gòu)師】,有意者簡歷至:[email protected] <<

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/88402.html

相關(guān)文章

  • 干貨 | 走進(jìn)Node.js啟動(dòng)過程剖析

    摘要:具體調(diào)用鏈路如圖函數(shù)主要是解析啟動(dòng)參數(shù),并過濾選項(xiàng)傳給引擎。查閱文檔之后發(fā)現(xiàn),通過指定參數(shù)可以設(shè)置線程池大小。原來的字節(jié)碼編譯優(yōu)化還有都是通過多線程完成又繼續(xù)深入調(diào)查,發(fā)現(xiàn)環(huán)境變量會影響的線程池大小。執(zhí)行過程如下調(diào)用執(zhí)行。 作者:正龍 (滬江Web前端開發(fā)工程師)本文原創(chuàng),轉(zhuǎn)載請注明作者及出處。 隨著Node.js的普及,越來越多的開發(fā)者使用Node.js來搭建環(huán)境,也有很多公司開始把...

    luck 評論0 收藏0
  • 系列3|走進(jìn)Node.js多進(jìn)程模型

    摘要:例如,在方法中,如果需要主從進(jìn)程之間建立管道,則通過環(huán)境變量來告知從進(jìn)程應(yīng)該綁定的相關(guān)的文件描述符,這個(gè)特殊的環(huán)境變量后面會被再次涉及到。 文:正龍(滬江網(wǎng)校Web前端工程師)本文原創(chuàng),轉(zhuǎn)載請注明作者及出處 之前的文章走進(jìn)Node.js之HTTP實(shí)現(xiàn)分析中,大家已經(jīng)了解 Node.js 是如何處理 HTTP 請求的,在整個(gè)處理過程,它僅僅用到單進(jìn)程模型。那么如何讓 Web 應(yīng)用擴(kuò)展到...

    snowell 評論0 收藏0
  • 走進(jìn)Node.js HTTP實(shí)現(xiàn)分析

    摘要:事實(shí)上,協(xié)議確實(shí)是基于協(xié)議實(shí)現(xiàn)的。的可選參數(shù)用于監(jiān)聽事件另外,它也監(jiān)聽事件,只不過回調(diào)函數(shù)是自己實(shí)現(xiàn)的。并且會把本次連接的套接字文件描述符封裝成對象,作為事件的參數(shù)。過載保護(hù)理論上,允許的同時(shí)連接數(shù)只與進(jìn)程可以打開的文件描述符上限有關(guān)。 作者:正龍(滬江Web前端開發(fā)工程師)本文為原創(chuàng)文章,轉(zhuǎn)載請注明作者及出處 上文走進(jìn)Node.js啟動(dòng)過程中我們算是成功入門了。既然Node.js的強(qiáng)...

    April 評論0 收藏0
  • Node.js 進(jìn)程平滑離場剖析

    摘要:在談如何做到進(jìn)程平滑離場前,我們需要一種機(jī)制,這種機(jī)制能讓我們主動(dòng)通知進(jìn)程何時(shí)離場,這就涉及到進(jìn)程間通信的知識了,我們先簡單了解下。進(jìn)程間通信對或類系統(tǒng)而言,進(jìn)程間通信的方式有很多種信號是其中的一種。 本文由云+社區(qū)發(fā)表作者:草小灰 使用 Node.js 搭建 HTTP Server 已是司空見慣的事。在生產(chǎn)環(huán)境中,Node 進(jìn)程平滑重啟直接關(guān)系到服務(wù)的可靠性,它的重要性不容我們忽視...

    xbynet 評論0 收藏0
  • 《阿里云前端技術(shù)周刊》第二期

    摘要:作者也樹校對染陌素材也樹英布阿里云前端技術(shù)周刊由阿里云智能商業(yè)中臺體驗(yàn)技術(shù)團(tuán)隊(duì)整理編寫。如何在工作中快速成長致工程師的個(gè)簡單技巧工程師成長干貨,全文提綱如下,圖片來自阿里技術(shù)公眾號關(guān)于我們我們是阿里云智能中臺體驗(yàn)技術(shù)團(tuán)隊(duì)。 作者:@也樹 校對:@染陌 素材:@也樹、@英布 《阿里云前端技術(shù)周刊》由阿里云智能商業(yè)中臺體驗(yàn)技術(shù)團(tuán)隊(duì)整理編寫。 知乎:阿里云中臺前端/全棧團(tuán)隊(duì)專欄 Github...

    kyanag 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<