跨平台底層函式庫https://github.com/hujianzhe/util, 下載後置於BootServer目錄下
簡介:
程式碼只實現服務節點啟動自舉、任務調度、基本模組描述,不包含任何業務程式碼,純C實現,一般編譯成動態函式庫使用功能方面保持極度克制,實現了一些常見通用協定流支援了C++ 20無堆疊協程的擴展,並且和純C動態庫部分程式碼隔離,防止C++污染到框架層,影響動態庫的生成
util中的程式碼負責跨平台,除了它不需要安裝任何第三方程式庫
運作流程簡介:
業務進程呼叫程式碼編譯成的動態函式庫,並呼叫對應介面使用,呼叫流程詳見測試節點範例(BootServer目錄下main_template是一套流程化的啟動程式碼範本)
模組內部由多個線程處理網路io讀寫,單獨的accept存取線程,初始時會開啟一個工作線程處理內部訊息和收到的網路訊息並派遣到你的業務程式碼邏輯中工作執行緒使用有堆疊協程進行調度處理(未使用無堆疊協程的原因見下文)
工作執行緒預設使用有棧協程進行調度處理以保持最高相容度,也可以很方便的使用C++20無棧協程(util庫中實現了一個)去調度,有棧協程與無棧協程可以共存
模組介紹與範例程式碼:
1、BootServer:主要程式碼部分,服務節點的必備初始化與操作
2、ServiceTemplate:服務節點程式碼模板,用來寫你的業務邏輯
3、SoTestClient,SoTestServer:測試節點,編寫了一些範例程式碼
4、Cpp20-SoTestServer:測試節點,編寫了一些範例程式碼,main.cpp中啟用了C++20無堆疊協程(使用util庫中的一個C++20無堆疊協程調度器)
編譯:
windows直接VS編譯
linux/Mac OS X下使用make debug / make release / make asan
啟動:
編輯好服務節點啟動需要的設定檔(具體格式參考附帶的設定檔範本),給每個節點一個設定檔和唯一id,日誌標識名,IP和埠號
windows直接VS打開,工程配置好啟動參數<設定檔>
linux/mac編譯後,sh run.sh <服務程序> <設定檔>
一些設計原因與見解: Q:為何不使用無堆疊協程而採用有棧協程? A:純C實作無堆疊協程是容易的(詳細程式碼可以看util函式庫中用純C實現的無堆疊協程調度器),但資源回收與持久化(尤其是堆疊上變數在協程重入後的情形)是極度困難的想順手的使用無棧協程還是得靠編譯器支持,這點C++20已經做到,util庫中也有完整的C++20無棧協程調度器實現
Q:為何無堆疊協程採用頭檔形式來提供這個擴充功能?
A:1.因為無堆疊協程具有程式碼侵入性,會改變大量函數簽章形式
2.包含了C++對象,這些在C中都是無法辨識的,無法順利導出動態函式庫
3.頭檔形式給出,將無堆疊協程的啟用權限交給應用層,是我目前想到的對純C動態函式庫部分沒有任何污染的應對方式
Q:可以替換成其他調度器麼?
A:可以取代工作執行緒的調度器,工作執行緒設計成調度器的運行載體,框架內部網路執行緒與工作執行緒的交互可透過介面hook對應調度行為
Q:可以替換成其他網路庫麼?
A:1.目前不可以替換成其他網路庫,但設計之初已經考慮這個問題,網路部分和任務調度部分是徹底分離的,將來會提供類似工作線程hook對應行為進行替換
2.雖然第三方網路庫很多,但其重點各不相同。有應用程式開發的tcp,udp函式庫,也有恨不得包含整個宇宙的函式庫,也有應用層實作整個網路協定棧的函式庫,所以其實這一塊並不是統一的
3.如果將來有一套網路庫進入了標準,那麼我會替換的
Q:為何不直接用C++做框架?
A:1.寫這套程式碼時候,C++20還沒出現,那時候相對最成熟的協程方案就是有棧協程,這個用C透過呼叫對應平台系統API就可以做到
2.這類框架要實現的功能已經固化,資源的生命週期都是流程固化的,用純C實現恰好足夠(之前有一個C++實現的版本,程式碼更複雜了)
3.如果動態函式庫被其他模組調用,還是需要C封一層接口
4.C++導出class具有傳染性,且ABI不統一
Q:業務層能繼續使用純C開發麼?
A:非常不建議,因為非同步流程的編寫,以及高階語言編寫的模組中拋出了異常,這些都讓資源銷毀的時機變得無法確定,這時仍用純C去人為手動控制資源是極度困難的應該使用更高階的語言去處理這些事情,例如可以用C++開發上層業務程式碼,它的RAII機制可以確保對應資源的釋放
Q:老專案的"回呼"想改造成"協程",是不是把類似調度器部分和業務代碼中的對應調用處替換就可以了?
A:沒有那麼簡單,首先是工作量問題,另外不論是callback形式還是協程形式,本質都是發出請求等待結果,這期間變數的生命週期是個必須要解決的問題,需要全盤仔細考慮,如果是原始指針,那麼幾乎可以說是無法改造的如果項目已經使用諸如std::shared_ptr之類的手段拉長了變數生命週期,那麼改造成為"有棧協程"的版本難度會少一些如果要改造成使用C++20無棧協程,工作量是巨大的無異於重寫專案(因為無堆疊協程具有強烈的程式碼侵入性)因此舊專案建議不改造
Q:為何目前不允許協程遷移到不同執行緒執行?
A:遷移協程可以很容易做到,但不提供的原因是
1.協程之間執行的任務是不確定的,可能造成io與計算混在同一個調度執行緒中
2.遷移後同一個協程過程可能會運行在不同線程上,這時必須保證你的程式碼不依賴任何線程本地變量,但你無法確保第三方庫沒有使用線程本地變量
Q:asan編譯運行時候會崩潰?
A:1.如果使用的是框架預設的有棧協程,確定不是程式碼方面的問題,可以調整節點設定檔中的有堆疊協程棧大小(ASAN啟用時,會消耗比較大的堆疊空間,因此有爆棧的可能性)
2.ASAN不完全支援unix環境的有棧協程API(ucontext),雖然util程式碼中已經對ucontext協程API進行了ASAN的適配,但仍不敢100%保證ASAN環境下運作不會出問題(截至目前為止一切良好)
3.在比較新的一些linux發行版中,ASAN如果發生無限輸出AddressSanitizer:DEADLYSIGNAL的情況,調整sudo sysctl vm.mmap_rnd_bits=28解決
TODO:
1、一個詳細的說明文檔,實在沒時間寫
2、對腳本語言編寫業務邏輯提供支持