国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

干貨 | 走進Node.js之啟動過程剖析

luck / 2737人閱讀

摘要:具體調用鏈路如圖函數主要是解析啟動參數,并過濾選項傳給引擎。查閱文檔之后發現,通過指定參數可以設置線程池大小。原來的字節碼編譯優化還有都是通過多線程完成又繼續深入調查,發現環境變量會影響的線程池大小。執行過程如下調用執行。

作者:正龍 (滬江Web前端開發工程師)
本文原創,轉載請注明作者及出處。

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

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

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

我們打開Node.js的二進制發布包,里面內容很簡單:node.exe、npm和node.h頭文件。node.h頭文件只有開發Node.js插件時才會用到。當我們啟動node.exe時,它到底做了哪些事情?

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

Init函數主要是解析Node.js啟動參數,并過濾V8選項傳給JavaScript引擎。

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

也就是說Node.js的主線程主要消費來自UV默認事件循環(uv_default_loop)和V8的MainThreadQueue和MainThreadDelayedQueue的任務。uv_run是一個阻塞調用。如果隊列中有任務,則執行并返回true,如果沒有的話,會阻塞住當前線程;如果返回false,則整個Node.js進程會釋放資源并退出。注意參數UV_RUN_ONCE,意思是從隊列中只取一個任務執行,不管隊列中當前是否有多個任務。

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

實際上,Node.js進程當前有7個線程。查閱文檔之后發現,Node.js通過指定參數--v8-pool-size可以設置V8線程池大小。原來V8的字節碼編譯、優化還有GC都是通過多線程完成;又繼續深入調查,發現環境變量UV_THREADPOOL_SIZE會影響libuv的線程池大小。

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

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

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

從Args中獲取到模塊名稱。

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

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

調用get_builtin_module,參數是模塊名,get_builtin_module會從modlist_builtin列表中查找內置模塊,所有內置模塊和第三方擴展都記錄在modlist_builtin列表中。C/C++模塊通過NODE_MODULE_CONTEXT_AWARE_BUILTIN注冊,第三方擴展模塊通過NODE_MODULE注冊。最終都會調用node_module_register。node_module結構體包含注冊函數、模塊名稱、文件名稱等信息。

如果查找到,則返回對應模塊的exports。

如果模塊名是constants,則調用DefineContstants。

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

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

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

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

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

到這兒,我們可以總結2個問題:

Node.js里面自己提供的JavaScript庫是怎么實現的?

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

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

Node.js內置的JavaScript文件,通過js2c.py編譯生成臨時文件node_natives.h。

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

首先定義要導出的C++類,構造器可以傳入一個數值;調用成員方法PlusOne,數值自增1并返回當前值。

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;
    };
}

實現文件

    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());

執行結果如下:

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

iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、當當開售。

>> 滬江Web前端上海團隊招聘【Web前端架構師】,有意者簡歷至:zhouyao@hujiang.com <<

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/88435.html

相關文章

  • 干貨剖析 | 走進Node.js啟動過程

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

    Simon 評論0 收藏0
  • 系列3|走進Node.js多進程模型

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

    snowell 評論0 收藏0
  • 走進Node.js HTTP實現分析

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

    April 評論0 收藏0
  • Node.js 進程平滑離場剖析

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

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

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

    kyanag 評論0 收藏0

發表評論

0條評論

luck

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<