大道至簡, 返璞歸真.
前言
在發表這篇博文的前夕, 還有一些小夥伴在提問一些以下相關的問題:
-
效能怎麼樣?
-
是否容易上手?
-
開發目標在哪?
-
如何反饋問題?
-
對比行業內的lua開源專案有何優勢?
等等, 以上問題會在本文中一一介紹.
CF的起因
首先來聊聊情懷這個東西! 相信每一個行業內的從業者都或多或少有過一個夢, 這個夢叫做: "我到時候要開發一個XXX"!其實作者當初也是一樣.
每當半夜(凌晨)在加班、看文件、除錯的時候, 總會搜尋到一些幾年前或十幾年前的框架或入門demo。例如: tinyhttp, 連結的原始碼是一些同學fork的映象站。
每次看到這些內容或多或少都會激起心中那一絲絲快熄滅的熱情, 也許這就是最後對技術的渴望?
就是在動手建立專案之前還反覆問過自己是否要做? 能堅持下去麼?也許被噴都是一種奢望?
在心裡一一回答了這些問題後, 在2018年末建立了本專案.
說句實話! 一個網路開發框架最難的不是實現某個功能, 而是從零開始一步一步添磚加瓦的造輪子!
作為一個網路開發框架, 最重要的兩個功能肯定是需要的! 定時器庫、事件驅動庫. 如何抉擇?選項有2個: libev / libuv .
libev 成熟穩定、輕量級、unix like支援、容易嵌入;
libuv 比libev更加優秀,增加了許多功能(執行緒池、訊號、同步、鎖等等),封裝更加完善, 並且增加了windows支援;
複製程式碼
從cf框架開發之初選型來看, libuv絕對是目前最優解. 但是作者偏偏選擇了libev. 也從此開始, 艱辛的底層開發之路就此展開.
首先, 作者不讓使用者C/C++進行實際業務開發! 這樣做會讓使用者有較高的開發成本與學習成本, 而選擇一門較好的指令碼語言就顯得尤為重要.
作者對Lua還算是稍微熟悉一點, 所以就選了Lua作為業務指令碼語言。至於Lua語言的優勢這裡就不說了, 網上大把文章誇它的.
現在既然指令碼語言已經選定, 那麼就開始寫程式碼吧!Let's Lua.
CF的編寫之路
1. 網路層
首先, 我們來看一段C封裝給Lua呼叫的API程式碼:
LUAMOD_API int
luaopen_tcp(lua_State *L){
luaL_checkversion(L);
/* 新增SSL支援 */
SSL_library_init();
SSL_load_error_strings();
// CRYPTO_set_mem_functions(xmalloc, xrealloc, xfree);
// OpenSSL_add_ssl_algorithms();
/* 新增SSL支援 */
luaL_newmetatable(L, "__TCP__");
lua_pushstring (L, "__index");
lua_pushvalue(L, -2);
lua_rawset(L, -3);
lua_pushliteral(L, "__mode");
lua_pushliteral(L, "kv");
lua_rawset(L, -3);
luaL_Reg tcp_libs[] = {
{"read", tcp_read},
{"write", tcp_write},
{"ssl_read", tcp_sslread},
{"ssl_write", tcp_sslwrite},
{"stop", tcp_stop},
{"start", tcp_start},
{"close", tcp_close},
{"listen", tcp_listen},
{"connect", tcp_connect},
{"ssl_connect", tcp_sslconnect},
{"new", tcp_new},
{"new_ssl", ssl_new},
{"free_ssl", ssl_free},
{"new_server_fd", new_server_fd},
{"new_client_fd", new_client_fd},
{NULL, NULL}
};
luaL_setfuncs(L, tcp_libs, 0);
luaL_newlib(L, tcp_libs);
return 1;
}
複製程式碼
以上是TCP實現的C程式碼的片段, 有興趣閱讀原始碼的小夥伴請點選這裡;
眾所周知Lua沒有原生的Socket. 那麼就需要框架編寫者自己抽象底層邏輯重新實現一套API.
簡單的封裝Lua C庫誰都會, 而且也算不上是什麼難事. 但是我們的目的是將底層同步阻塞Socket hook為非阻塞, 這時候難點就來了!
大家都知道libev是基於react模型的事件驅動網路庫, 所有註冊事件後的業務邏輯都是以回撥的形式觸發. 那不就變成node-lua程式碼了嗎?(笑)
這時候, 作者想了個點子來解決這個問題! 執行流程如下:
- 每次需要做一些同步操作的時候, 就呼叫C API註冊回撥事件.
- 為當前註冊的所有事件建立一個Lua協程儲存上下文並讓出當前協程執行權.
- 等到註冊事件被觸發後, 呼叫C API恢復協程繼續執行.
簡單來說就是將C層次的非同步回撥邏輯封裝為Lua層的同步非阻塞, 保證不因為IO問題阻塞執行緒.
下面提供一段socket同步非阻塞的虛擬碼, 經供參考:
function TCP:recv(bytes)
local current_co = co_self()
self.read_co = read_ev(function()
-- do action
-- stop timer_ev
-- wakeup(current_co) 恢復執行權
end)
self.timer_co = self.timer_ev(function()
-- do action
-- stop read_ev
-- wakeup(current_co) 恢復執行權
end)
tcp_start(io, EV_READ, self.read_co)
timer_start(timer, 3秒超時, self.timer_co)
return co_yield() -- 讓出執行權
end
複製程式碼
一個Lua版的Socket EV_READ虛擬碼大致的處理流程如上, 想看實際處理邏輯請看這裡。
同理, Socket write/connect/listen等等API直接照抄就行(UDP也大同小異). (其實這裡有個小插曲就是SSL SOCKET的坑, 但是由於篇幅問題就不說了.)
細心的小夥伴可能發現程式碼同時註冊了Socket與Timer事件, Socket非阻塞操作不能解決read與connect超時的問題. 所以cf框架乾脆就封裝徹底一點.
至此, Socket算是已經算是基本hook與封裝完成了. 接下來就可以開始寫應用層協議了.
2. 應用層協議
現在Socket終於能正常使用了, 那麼面臨的新問題就又來了。
libev沒有自帶非同步dns
dns都還需要使用者自己封裝, 這個坑真是填的無比難受! 好在網路上有前輩實現了Lua版的非同步dns, 作者稍微看明白之後就借用了過來封裝內部使用.
這樣cf也算是有了深度定製的非同步dns庫了吧!(雖然並不完善, 但是足夠使用)
一個網路庫是否流行, 基本上就得看生態. 那麼協議層的輪子又得造起來:
其中一些協議為各位前輩那邊借過來適配後定製的, 簡單的協議則是直接花1-2小時直接手寫出來的。
3. 封裝與易用性
為了不讓API那麼封閉與提升cf的可用性, 作者決定將mysql與redis進行初步封裝.
封裝包括大家常用的功能, 連線池、物件導向操作、無需手動管理session生命週期等等. 簡化程式設計思想包袱來提升開發效率.
至於內部Socket更是讓框架來解決釋放問題確保檔案描述數量限制的情況下也是可以正常使用. (其實是不喜歡依賴gc被動close fd與free記憶體)
CF是啥?
如果你耐心看完了第一部分介紹, 那麼你就應該對cf有了一個大概的瞭解.
cf全稱為: CoreFramework, 是一個基於libev的Lua網路開發框架. 在其內部實現了多種網路協議與第三方庫用來幫助使用者進行專案原型的快速開發.
cf 在httpd使用上尊崇前、後端分離的解決方案, 僅實現了基本的view路由並且不支援rest風格的API路由. 雖然這樣可能會引來宇多人的詬病.
cf 的httpd內嵌websocket支援, 方便使用者在複用埠的同時也可以享受長連線編寫的樂趣.
更多的介紹, 請大家專案地址的Wiki
CF能做什麼?
-
基於容器技術的微服務場景(swarm/kubernetes); —— 推薦
-
遊戲伺服器的前端代理層; —— 推薦
-
記憶體/CPU資源較為緊缺的雲伺服器; —— 推薦
-
對效能要求較高的無狀態叢集; —— 推薦
-
海量長連線(websocket)Agent叢集; —— 推薦
CF使用到的技術棧?
傳輸層: TCP/UDP
會話層: SSL Client支援
協議層: dns/webocket/http/mqtt/redis/mysql/smtp
工具庫: Timer/TASK
第三方庫: Libev、openssl/libressl、lua-5.3、jemalloc/tcmalloc(可選)
CF如何安裝?
cf 目前支援絕大部分Unix like作業系統, 作者是在Mac上進行開發, 所以Mac支援是必須的.
cf測試的Linux為Centos, 所以基本上基於Linux核心的作業系統編譯後的執行也沒什麼問題(export 增加/usr/local/lib)
同時,作者還貼心的為大家做了一個簡單Dockerfile. 檔案在專案根目錄下, 大家下載直接使用即可。
當然, 如果你不想製作Dockerfile,也可以使用Docker命令直接拉去作者製作好放在docker hub的映象. candymi/cfweb
CF 如何執行呢?
測試執行
bash#: ./cfadmin
後臺執行
bash#: ./cfadmin
退出
killall cfadmin
ctrl + c
文件在哪?
作者為大家貼心的寫了一篇詳細到不能再詳細的文件, 以此來獲取大家的點贊與關注.
作者還為喜歡閱讀原始碼的同學準備了充足的中文註釋與英文註釋, 結合起來方便大家快速瞭解CF工作方式(中/英註釋結合易於理解一些專屬詞彙).
回答之前的問題:
Q. 效能怎麼樣?
A. 效能還不錯, 但是具體數值請自行測試.
Q. 是否容易上手?
A. 學習lua 一小時入門 -> cf 一小時入門
Q. 開這個專案的初衷是什麼?
A. 其實在前面已經回答過了.
Q. 開發目標在哪?
A. Wiki 裡有TODO項
Q. 如何反饋問題?
A. Wiki 裡有Q & A項
Q. 對比行業內的lua開源專案有何優勢?
A. CF對比其它lua開發專案更深入改變使用者使用習慣! 簡化框架上手難度, 將框架都黑盒子透明化. 無需學習複雜的設計模式與理念.
Q. CF的開發理念是什麼?
A. CF專案的目標不是競爭, 而是明白明白簡單為美. 當你習慣了它, 也許你就會上癮.
使用示例
精彩截圖
希望
也許你正在使用其它開發框架, 但是這不妨礙你對cf的督促.
也許你正在試用它, 這不妨礙你與作者溝通你的想法.
也許你正在吐槽它的缺點,請來issue盡情吐槽.