目的: 技術小白如果不小心打開了本文,直接拖到最後看總結即可 本文的閱讀對象是對 EOS 源碼感興趣的同學 通過本文,你會掌握閱讀每一個插件的步驟
之前我們通過 5 行代碼對 eosiod
的脈絡有了大致的瞭解,我們知道了它是一個插件化的服務,插件化服務的特點是可以像 USB 一樣即插即用,有着很高的靈活性。今天我們更深入一點,來看下 EOS
服務的插件是如何佈局的,以及如何閱讀每一個插件的代碼。
EOS.IO
的每個插件,在生命期內都會經歷以下階段
- 插件註冊
- 插件初始化
- 插件啓動
下面我們一一道來。
插件註冊
還記得上次我們說過的最後一行代碼嗎?
這行代碼會把不同的插件註冊到管理插件的插件容器中,剛開始,不是所有插件都會主動把自己註冊到系統中,我們來看下在初始狀態下,有哪些插件會主動註冊自己,如下圖:
可以看到,插件的命名很清晰,通過命名就可以判斷不同插件所具備的能力。例如 producer_plugin
肯定是和出塊節點相關的插件。
解決插件依賴
在上一篇文章中,由於篇幅原因,我們跳過了註冊過程中的一行代碼:
plug->register_dependencies();
從字面來看,的作用是註冊所依賴插件,今天我們就從這行代碼開始,它的實現如下所示:
它會調用每個插件的 plugin_requires
函數,但詭異的是,沒有任何一個插件有這個函數的實現,通過搜索發現,這個函數是由一個宏來定義的:
猜想只要插件類中包含了 APPBASE_PLUGIN_REQUIRES
宏,即是定義了 plugin_requires
函數,再查看不同插件中的頭文件,果不其然,註冊到系統中的插件都包含了該宏,下面是 3 個不同插件的例子
你一定發現了其中的差異,每個插件的參數是不同的,這是什麼情況?此時就得看 plugin_requires
的具體的實現了
plugin_requires
函數會調用 boost
庫中的一個宏——BOOST_PP_SEQ_FOR_EACH
,該宏的用法參見 http://t.cn/REqJmyG。
在本例子中,意爲對於 PLUGINS
中的每一個 PLUGIN
,都調用一次 register_plugin();
,如上圖所示。以 net_api_plugin
這個插件爲例,下面是該插件的展開式
清楚了吧,可以看到展開後,對所依賴的每個插件,會繼續調用 register_plugin
,形成了遞歸效果,直到遇到獨立的插件爲止,例如上面的 chain_plugin
就是一個獨立的插件。
翻譯爲白話就是:
在註冊一個插件之間,先把該插件所依賴的其他插件註冊到系統中。且無論依賴多少個插件,你只需要寫一行代碼。
在我看來,這樣的寫法是非常優雅的,設計上可以用精妙來形容,此時,系統中的插件佈局變成了這樣:
至此,插件的註冊部分就全部完成了,你可能已經發現了,上述插件有重複註冊的情況,但不用擔心,register_plugin
函數的實現會避免該情況的發生,如下 :
插件初始化
註冊完插件後,下一步就需要對這些插件初始化,我們回到之前介紹的「第 1 行」代碼,該代碼的調用次序由上至下如下圖所示,
可以看到初始化是由 initialize_impl
這個函數實現的,這個函數接收命令行選項,和 3 個自動啓動的插件作爲參數——chain_plugin
、http_plugin
和 net_plugin
(我用白色的下劃線做了標記)。
initialize_impl
做了 2 件事情:
- 獲取配置信息,包括命令行選項和配置文件中的參數
- 初始化插件
獲取配置信息這件事,EOS.IO
也藉助了 boost
庫,對於該知識的說明,你可以參考這篇文章:http://sina.lt/fpBy。
值得說明的事,爲了產生不同插件的特定配置項,每個插件都實現了 set_program_options
函數,以 http_plugin
爲例
上面代碼爲 http_plugin
提供了外部參數 http-server-address
,該參數既可以通過命令行獲取(由 plugin_cli_opts
和 plugin_cfg_opts
兩個變量控制),又可以通過配置文件獲取(由 plugin_cfg_opts
控制),於是,當你在命令行輸入 eosiod --help
時,會輸出以下信息
所有的參數信息最終會存儲在 options
這個變量中,然後在初始化時,傳入不同的插件
配置信息獲取成功後,就該對插件進行初始化了,需要對哪些插件初始化呢,來自 3 個途徑:
- 上文提到的自動啓動的插件
- 配置文件中描述的插件
- 1 和 2 所依賴的插件
第 1 個途徑上面已經介紹了,現在看下第 2 個途徑包含哪些插件,打開 config.ini
文件,可以看到以下插件列表
最後,我們再看下系統是如何初始化依賴插件的,在 initialize_impl
函數中,上述 2 個途徑的插件初始化都會調用 initialize
這個函數,這個函數的實現如下所示
進入 initialize
函數,你發現了什麼,plugin_requires
這個函數是不是非常熟悉?沒錯,上文已經花了大量的篇幅來解釋這個函數,但在初始化場景下有什麼不同呢?細心的你應該發現了,差異在於 Lambda
表達式上,我們再次拿 chain_api_plugin
這個插件舉例,嘗試對其進行展開
看出變化了吧,意思是
如果這個插件依賴其他插件,則先對其依賴的插件調用
initialize
,這些插件如果還依賴另外的插件,則先對另外的插件調用initialize
,依此遞歸下去……直到遇到獨立插件爲止
待「依賴鏈」全部初始化完成後,該插件纔會調用 plugin_initialize
對自己初始化,同樣,不同插件實現了不同的初始化方式。於是,插件的佈局及初始化部分就已經完成了。
不難發現,插件的啓動也是通過這種方式完成的,這一部分我就不展開了,留給你作爲作業吧:)
總結
本文主要分析了 EOS.IO
這個開源軟件的插件佈局,包括了插件在系統中的生命週期,同時我們也學習了一個非常 Tricky 的解依賴的編碼技巧,當我第一次看到這樣的實現時,我是這樣感嘆的:
eos 裏插件的設計都是鏈式的,太 BT 了
敲黑板了,通過以上內容,你在今後學習每個插件時,其實只需記住 3 個函數即可,它們是每個插件中的
set_program_options
:用來設置插件的參數plugin_initialize
:實現了插件具體的初始化過程plugin_startup
:運行插件
_ 免責聲明:以上所有代碼分析和對代碼的感受,都不構成任何投資建議,謝謝 _
文:馮雅傑
來源:微信公衆號:程序員在深圳(http://mp.weixin.qq.com/s/9Iw-CcXv98CaebdBmsv7xg) 版權聲明:
作者保留權利。文章爲作者獨立觀點,不代表巴比特立場。
來源鏈接:www.8btc.com