目的:

技術小白如果不小心打開了本文,直接拖到最後看總結即可

本文的閱讀對象是對 EOS 源碼感興趣的同學

通過本文,你會掌握閱讀每一個插件的步驟

之前我們通過 5 行代碼對 eosiod 的脈絡有了大致的瞭解,我們知道了它是一個插件化的服務,插件化服務的特點是可以像 USB 一樣即插即用,有着很高的靈活性。今天我們更深入一點,來看下 EOS 服務的插件是如何佈局的,以及如何閱讀每一個插件的代碼。

EOS.IO 的每個插件,在生命期內都會經歷以下階段

  1. 插件註冊
  2. 插件初始化
  3. 插件啓動

下面我們一一道來。

插件註冊

還記得上次我們說過的最後一行代碼嗎?

1933644-50c0c46e5539161a

這行代碼會把不同的插件註冊到管理插件的插件容器中,剛開始,不是所有插件都會主動把自己註冊到系統中,我們來看下在初始狀態下,有哪些插件會主動註冊自己,如下圖:

eos_plugins

可以看到,插件的命名很清晰,通過命名就可以判斷不同插件所具備的能力。例如 producer_plugin 肯定是和出塊節點相關的插件。

解決插件依賴

在上一篇文章中,由於篇幅原因,我們跳過了註冊過程中的一行代碼:

plug->register_dependencies();

從字面來看,的作用是註冊所依賴插件,今天我們就從這行代碼開始,它的實現如下所示:

carbon

它會調用每個插件的 plugin_requires 函數,但詭異的是,沒有任何一個插件有這個函數的實現,通過搜索發現,這個函數是由一個宏來定義的:

carbon (1)

猜想只要插件類中包含了 APPBASE_PLUGIN_REQUIRES 宏,即是定義了 plugin_requires 函數,再查看不同插件中的頭文件,果不其然,註冊到系統中的插件都包含了該宏,下面是 3 個不同插件的例子

carbon (2)

你一定發現了其中的差異,每個插件的參數是不同的,這是什麼情況?此時就得看 plugin_requires 的具體的實現了

carbon (3)

plugin_requires 函數會調用 boost 庫中的一個宏——BOOST_PP_SEQ_FOR_EACH,該宏的用法參見 http://t.cn/REqJmyG

在本例子中,意爲對於 PLUGINS 中的每一個 PLUGIN,都調用一次 register_plugin();,如上圖所示。以 net_api_plugin 這個插件爲例,下面是該插件的展開式

carbon (4)

清楚了吧,可以看到展開後,對所依賴的每個插件,會繼續調用 register_plugin,形成了遞歸效果,直到遇到獨立的插件爲止,例如上面的 chain_plugin 就是一個獨立的插件。

翻譯爲白話就是

在註冊一個插件之間,先把該插件所依賴的其他插件註冊到系統中。且無論依賴多少個插件,你只需要寫一行代碼。

在我看來,這樣的寫法是非常優雅的,設計上可以用精妙來形容,此時,系統中的插件佈局變成了這樣:

eos_plugins2

至此,插件的註冊部分就全部完成了,你可能已經發現了,上述插件有重複註冊的情況,但不用擔心,register_plugin 函數的實現會避免該情況的發生,如下 :

carbon (5)

插件初始化

註冊完插件後,下一步就需要對這些插件初始化,我們回到之前介紹的「第 1 行」代碼,該代碼的調用次序由上至下如下圖所示,

carbon (6)

可以看到初始化是由 initialize_impl 這個函數實現的,這個函數接收命令行選項,和 3 個自動啓動的插件作爲參數——chain_pluginhttp_pluginnet_plugin(我用白色的下劃線做了標記)。

initialize_impl 做了 2 件事情:

  1. 獲取配置信息,包括命令行選項和配置文件中的參數
  2. 初始化插件

獲取配置信息這件事,EOS.IO 也藉助了 boost 庫,對於該知識的說明,你可以參考這篇文章:http://sina.lt/fpBy

值得說明的事,爲了產生不同插件的特定配置項,每個插件都實現了 set_program_options 函數,以 http_plugin 爲例

carbon (7)

上面代碼爲 http_plugin 提供了外部參數 http-server-address,該參數既可以通過命令行獲取(由 plugin_cli_optsplugin_cfg_opts 兩個變量控制),又可以通過配置文件獲取(由 plugin_cfg_opts 控制),於是,當你在命令行輸入 eosiod --help 時,會輸出以下信息

carbon (8)

所有的參數信息最終會存儲在 options 這個變量中,然後在初始化時,傳入不同的插件

carbon (9)

配置信息獲取成功後,就該對插件進行初始化了,需要對哪些插件初始化呢,來自 3 個途徑:

  1. 上文提到的自動啓動的插件
  2. 配置文件中描述的插件
  3. 1 和 2 所依賴的插件

第 1 個途徑上面已經介紹了,現在看下第 2 個途徑包含哪些插件,打開 config.ini 文件,可以看到以下插件列表

carbon (10)

最後,我們再看下系統是如何初始化依賴插件的,在 initialize_impl 函數中,上述 2 個途徑的插件初始化都會調用 initialize 這個函數,這個函數的實現如下所示

carbon (11)

進入 initialize 函數,你發現了什麼,plugin_requires 這個函數是不是非常熟悉?沒錯,上文已經花了大量的篇幅來解釋這個函數,但在初始化場景下有什麼不同呢?細心的你應該發現了,差異在於 Lambda 表達式上,我們再次拿 chain_api_plugin 這個插件舉例,嘗試對其進行展開

carbon (12)

看出變化了吧,意思是

如果這個插件依賴其他插件,則先對其依賴的插件調用 initialize,這些插件如果還依賴另外的插件,則先對另外的插件調用 initialize,依此遞歸下去……直到遇到獨立插件爲止

待「依賴鏈」全部初始化完成後,該插件纔會調用 plugin_initialize 對自己初始化,同樣,不同插件實現了不同的初始化方式。於是,插件的佈局及初始化部分就已經完成了。

不難發現,插件的啓動也是通過這種方式完成的,這一部分我就不展開了,留給你作爲作業吧:)

總結

本文主要分析了 EOS.IO 這個開源軟件的插件佈局,包括了插件在系統中的生命週期,同時我們也學習了一個非常 Tricky 的解依賴的編碼技巧,當我第一次看到這樣的實現時,我是這樣感嘆的:

eos 裏插件的設計都是鏈式的,太 BT 了

敲黑板了,通過以上內容,你在今後學習每個插件時,其實只需記住 3 個函數即可,它們是每個插件中的

  1. set_program_options:用來設置插件的參數
  2. plugin_initialize:實現了插件具體的初始化過程
  3. plugin_startup:運行插件

_ 免責聲明:以上所有代碼分析和對代碼的感受,都不構成任何投資建議,謝謝 _

文:馮雅傑
來源:微信公衆號:程序員在深圳(http://mp.weixin.qq.com/s/9Iw-CcXv98CaebdBmsv7xg) 版權聲明:
by
nc"
sa

作者保留權利。文章爲作者獨立觀點,不代表巴比特立場。

來源鏈接:www.8btc.com