Skynet服務器框架(一) Linux下的安裝和啟動
根據雲風博客的描述,Skynet
的核心功能就是解決一個問題:
把一個符合規範的 C
模塊,從 動態庫
(so文件)中啟動起來,綁定一個永不重復(即使模塊退出)的數字id
做為其 handle
。模塊
被稱為 服務
(Service),服務間可以自由發送消息。
- 每個
模塊
可以向 Skynet 框架註冊一個callback
函數,用來接收發給它的消息; - 每個服務都是被一個個
消息包
驅動,當沒有包到來的時候,它們就會處於掛起狀態
,此狀態對 CPU 資源零消耗。如果需要自主邏輯,則可以利用 Skynet 系統提供的timeout消息
,定期觸發。
名字服務:
Skynet 提供了名字服務
,還可以給特定的服務起一個易讀的名字,而不是用 id 來指代它。id 和運行時態相關,無法保證每次啟動服務,都有一致的 id ,但名字可以。
簡而言之,這個框架完成的功能大概如下: Skynet
只負責把一個數據包從一個服務內發送出去,讓同一進程內的另一個服務收到,調用對應的callback
函數處理。它保證,模塊的初始化過程,每個獨立的 callback
調用,都是 相互線程安全
的。編寫服務的人不需要特別的為多線程環境考慮任何問題,專心處理發送給它的一個個數據包。
2.框架優點:
-
高低級語言配合:
Skynet
是一個融合了低級語言(C)消息框架和高級動態語言(lua)的混合體,這種結構稱為hybrid framework
C
來寫服務節點,也可以選擇同樣開發高效而且安全隔離的lua
來寫上層業務。Skynet
的主要核心包括兩部分:C語言
實現的消息循環和組件加載機制;lua
實現的以消息為中心的進入退出coroutine
(協程)的包裝層。
-
組件化能力:
Skynet
內核(C部分)自身支持加載模塊(.so
文件),你可以使用C語言去寫性能有要求的服務節點,通過消息與其他節點配合。lua又是一個對C語言極為友好的動態語言,所以你可以找到很多現成的lua的C擴展,skynet/3rd
路徑下可以放置你需要的各種組件,比如:CJSON
、sqlite
和OpenSSL
等。
3.單進程:
很多服務器框架在構建之初,就設想著用多進程的方式來解決高並發的問題,但是所帶來的問題就是多進程不可避免的進程安全鎖,這樣的框架經常會因為部分代碼的報錯而導致死鎖或者內存占用不釋放等問題。很多優秀的服務器框架都是使用單進程,然後通過線程池來做消息輪詢和任務執行的方式來實現的,這樣能夠避開鎖所帶來的諸多問題。
Skynet也是單進程的服務器框架,在單一進程上啟動一個線程池,其中包括多個 worker
線程 、一個 socket
網絡線程和一個 timer
時間線程。當創建了多個 lua服務
,每個服務都相當於Erlang中的一個 Actor
(可以簡單理解為:可以並行運行的對象),每個服務都有自己的消息隊列,skynet也有一個全局的消息隊列,線程池中的 worker
線程會隨機從消息隊列中取出消息來執行直到消息隊列為空。此外,每個 lua服務
中又可以通過啟動多個 coroutine
(攜程)來實現異步操作的目的。
Skynet下載配置:
要學習開源框架,第一步肯定是先拿來試用一下,然後再取剖析源碼,接下來我們就嘗試下載Skynet
框架,並嘗試使用這套開源的框架來搭建一個測試服務器:
1.資源下載:
Github源碼地址:cloudwu/skynet
假如當前是在Linux環境下,並已經安裝有 git
工具,則可以直接使用 git
指令 git clone
來拉取Github倉庫的 Skynet
最新源碼:
sudo git clone https://github.com/cloudwu/skynet.git
- 1
假如執行正常,輸出如下:
linsh@ubuntu:/mnt/Windows$ sudo git clone https://github.com/cloudwu/skynet.git
正克隆到 ‘skynet‘...
remote: Counting objects: 8079, done.
remote: Compressing objects: 100% (22/22), done.
remote: Total 8079 (delta 1), reused 0 (delta 0), pack-reused 8057
接收對象中: 100% (8079/8079), 2.72 MiB | 24.00 KiB/s, done.
處理 delta 中: 100% (5442/5442), done.
檢查連接... 完成。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
假如還沒安裝git
工具,可以通過以下指令安裝(我的操作系統是 Ubuntu14.04.4
):
sudo apt-get install git
- 1
2.源碼目錄結構:
關於源碼主要目錄及其作用如下:
skynet-master
--3rd //第三方代碼,主要生產一些給lua用的so動態庫
--example //示例工程
--lualib //lua庫
--lualib-src //luaclib:給lua用的c庫
--service //lua服務
--service-src //csservice:c服務
--skynet-src //skynet核心c源碼主程序
--test //一些類庫和接口調用的客戶端用例
--HISTORY.md //版本更新日誌
--LICENSE
--Makefile //編譯腳本
--platform.mk //運行平臺相關(支持Linux、MacOSX、freebsd操作系統)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
3.源碼編譯:
-
工具安裝:
在編譯前還需要安裝兩個工具,不然會出現報錯:-
安裝
autoconf
:sudo apt-get install autoconf
- 1
否則會報如下錯誤:
cd 3rd/jemalloc && ./autogen.sh --with-jemalloc-prefix=je_ --disable-valgrind autoconf ./autogen.sh: line 5: autoconf: command not found Error 0 in autoconf make[1]: *** [3rd/jemalloc/Makefile] Error 1 make[1]: Leaving directory `/data/skynet‘ make: *** [linux] Error 2
- 1
- 2
- 3
- 4
- 5
- 6
- 7
-
安裝
readline-devel
:sudo apt-get install libreadline-dev
- 1
否則會報如下錯誤:
lua.c:83:31: fatal error: readline/readline.h: 沒有那個文件或目錄 #include <readline/readline.h> ^ compilation terminated. make[3]: *** [lua.o] 錯誤 1 make[3]:正在離開目錄 `/application/skynet/3rd/lua‘ make[2]: *** [linux] 錯誤 2 make[2]:正在離開目錄 `/application/skynet/3rd/lua‘ make[1]: *** [3rd/lua/liblua.a] 錯誤 2 make[1]:正在離開目錄 `/application/skynet‘ make: *** [linux] 錯誤 2
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
-
-
編譯操作:
由於下載的是源碼,需要進過編譯才能運行,編譯過程就是:-
進入
clone
到本地的項目目錄,執行make
指令編譯源碼:cd skynet sudo make linux
- 1
- 2
假如編譯過程正常,編譯完成後如下:
linsh@ubuntu:/application/skynet$ sudo make linux make all PLAT=linux SKYNET_LIBS="-lpthread -lm -ldl -lrt" SHARED="-fPIC --shared" EXPORT="-Wl,-E" MALLOC_STATICLIB="3rd/jemalloc/lib/libjemalloc_pic.a" SKYNET_DEFINES="" make[1]: 正在進入目錄 `/application/skynet‘ cc -g -O2 -Wall -I3rd/lua -o skynet skynet-src/skynet_main.c skynet-src/skynet_handle.c skynet-src/skynet_module.c skynet-src/skynet_mq.c skynet-src/skynet_server.c skynet-src/skynet_start.c skynet-src/skynet_timer.c skynet-src/skynet_error.c skynet-src/skynet_harbor.c skynet-src/skynet_env.c skynet-src/skynet_monitor.c skynet-src/skynet_socket.c skynet-src/socket_server.c skynet-src/malloc_hook.c skynet-src/skynet_daemon.c skynet-src/skynet_log.c 3rd/lua/liblua.a 3rd/jemalloc/lib/libjemalloc_pic.a -Iskynet-src -I3rd/jemalloc/include/jemalloc -Wl,-E -lpthread -lm -ldl -lrt mkdir cservice cc -g -O2 -Wall -I3rd/lua -fPIC --shared service-src/service_snlua.c -o cservice/snlua.so -Iskynet-src cc -g -O2 -Wall -I3rd/lua -fPIC --shared service-src/service_logger.c -o cservice/logger.so -Iskynet-src cc -g -O2 -Wall -I3rd/lua -fPIC --shared service-src/service_gate.c -o cservice/gate.so -Iskynet-src cc -g -O2 -Wall -I3rd/lua -fPIC --shared service-src/service_harbor.c -o cservice/harbor.so -Iskynet-src mkdir luaclib cc -g -O2 -Wall -I3rd/lua -fPIC --shared lualib-src/lua-skynet.c lualib-src/lua-seri.c -o luaclib/skynet.so -Iskynet-src -Iservice-src -Ilualib-src cc -g -O2 -Wall -I3rd/lua -fPIC --shared lualib-src/lua-socket.c -o luaclib/socketdriver.so -Iskynet-src -Iservice-src cc -g -O2 -Wall -I3rd/lua -fPIC --shared -Iskynet-src lualib-src/lua-bson.c -o luaclib/bson.so -Iskynet-src cc -g -O2 -Wall -I3rd/lua -fPIC --shared lualib-src/lua-mongo.c -o luaclib/mongo.so -Iskynet-src cc -g -O2 -Wall -I3rd/lua -fPIC --shared -I3rd/lua-md5 3rd/lua-md5/md5.c 3rd/lua-md5/md5lib.c 3rd/lua-md5/compat-5.2.c -o luaclib/md5.so cc -g -O2 -Wall -I3rd/lua -fPIC --shared lualib-src/lua-netpack.c -Iskynet-src -o luaclib/netpack.so cc -g -O2 -Wall -I3rd/lua -fPIC --shared lualib-src/lua-clientsocket.c -o luaclib/clientsocket.so -lpthread cc -g -O2 -Wall -I3rd/lua -fPIC --shared -Iskynet-src lualib-src/lua-memory.c -o luaclib/memory.so cc -g -O2 -Wall -I3rd/lua -fPIC --shared lualib-src/lua-profile.c -o luaclib/profile.so cc -g -O2 -Wall -I3rd/lua -fPIC --shared -Iskynet-src lualib-src/lua-multicast.c -o luaclib/multicast.so cc -g -O2 -Wall -I3rd/lua -fPIC --shared -Iskynet-src lualib-src/lua-cluster.c -o luaclib/cluster.so cc -g -O2 -Wall -I3rd/lua -fPIC --shared lualib-src/lua-crypt.c lualib-src/lsha1.c -o luaclib/crypt.so cc -g -O2 -Wall -I3rd/lua -fPIC --shared -Iskynet-src lualib-src/lua-sharedata.c -o luaclib/sharedata.so cc -g -O2 -Wall -I3rd/lua -fPIC --shared -Iskynet-src lualib-src/lua-stm.c -o luaclib/stm.so cc -g -O2 -Wall -I3rd/lua -fPIC --shared -Ilualib-src/sproto lualib-src/sproto/sproto.c lualib-src/sproto/lsproto.c -o luaclib/sproto.so cc -g -O2 -Wall -I3rd/lua -fPIC --shared -I3rd/lpeg 3rd/lpeg/lpcap.c 3rd/lpeg/lpcode.c 3rd/lpeg/lpprint.c 3rd/lpeg/lptree.c 3rd/lpeg/lpvm.c -o luaclib/lpeg.so cc -g -O2 -Wall -I3rd/lua -fPIC --shared lualib-src/lua-mysqlaux.c -o luaclib/mysqlaux.so cc -g -O2 -Wall -I3rd/lua -fPIC --shared -Iskynet-src lualib-src/lua-debugchannel.c -o luaclib/debugchannel.so make[1]:正在離開目錄 `/application/skynet‘
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
-
4.錯誤集:
-
錯誤一:
由於系統時間設置錯誤,導致編譯進入死循環,報錯如下:make[2]: *** 警告:文件“Makefile.in”的修改時間在將來1.8e+06
- 1
解決方案:
- 假如當前系統時間是錯誤的,修正系統時間即可;
- 假如是文件時間戳有誤,可以使用
ind ./* -exec touch {} +
修正文件的時間戳。
-
錯誤二:
在虛擬機的共享目錄下安裝skynet
,由於虛擬機共享目錄不能設置軟連接:ln -sf libjemalloc.so.2 lib/libjemalloc.so ln: 無法創建符號鏈接"lib/libjemalloc.so": 不支持的操作 make[2]: *** [lib/libjemalloc.so] 錯誤 1 make[2]:正在離開目錄 `/mnt/Windows/skynet/3rd/jemalloc‘ make[1]: *** [3rd/jemalloc/lib/libjemalloc_pic.a] 錯誤 2 make[1]:正在離開目錄 `/mnt/Windows/skynet‘ make: *** [linux] 錯誤 2
- 1
- 2
- 3
- 4
- 5
- 6
- 7
解決方案:
選擇其他非共享的目錄進行安裝,例如直接復制:sudo cp -r skynet /application/
- 1
啟動流程:
skynet
由一個或多個進程構成,每個進程被稱為一個 skynet 節點
。接下來嘗試實現 skynet 節點
的啟動流程。
1.配置文件Config
上面完成了源碼編譯,但是運行啟動指令的時候,需要傳入一個 Config文件
名稱作為啟動參數,skynet
會讀取這個 Config文件
獲取一些啟動的必要參數,所以在運行程序之前,還需要根據要求修改配置文件,可以參考 example/config
或直接對其進行修改:
root = "./"
thread = 8
logger = nil
harbor = 1
address = "127.0.0.1:2526"
master = "127.0.0.1:2013"
start = "main" -- main script
bootstrap = "snlua bootstrap" -- The service for bootstrap
standalone = "0.0.0.0:2013"
luaservice = root.."service/?.lua;"..root.."test/?.lua;"..root.."examples/?.lua"
lualoader = "lualib/loader.lua"
snax = root.."examples/?.lua;"..root.."test/?.lua"
cpath = root.."cservice/?.so"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
不難看出,這個配置文件內存其實是一個lua代碼,以 key-value
形式進行賦值,skynet
啟動時讀取必要配置項,其他項即便用不到也會以字符串的形式存入 env
表中,所有配置項都可通過 skynet.getenv
獲取。
-
必要的配置項有:
- thread 啟動多少個工作線程。通常不要將它配置超過你實際擁有的 CPU 核心數。
- bootstrap skynet 啟動的第一個服務以及其啟動參數。默認配置為
snlua bootstrap
,即啟動一個名為bootstrap
的 lua 服務。通常指的是service/bootstrap.lua
這段代碼。 - cpath 用 C 編寫的服務模塊的位置,通常指
cservice
下那些.so
文件。如果你的系統的動態庫不是以.so
為後綴,需要做相應的修改。這個路徑可以配置多項,以;
分割。
-
在默認的 bootstrap 代碼中還會進一步用到一些配置項:
- logger 它決定了 skynet 內建的
skynet_error
這個 C API 將信息輸出到什麽文件中。如果logger
配置為nil
,將輸出到標準輸出。你可以配置一個文件名來將信息記錄在特定文件中。 - logservice 默認為
"logger"
,你可以配置為你定制的 log 服務(比如加上時間戳等更多信息)。可以參考service_logger.c
來實現它。註:如果你希望用 lua 來編寫這個服務,可以在這裏填寫snlua
,然後在 logger 配置具體的 lua 服務的名字。在examples
目錄下,有config.userlog
這個範例可供參考。 - logpath 配置一個路徑,當你運行時為一個服務打開 log 時,這個服務所有的輸入消息都會被記錄在這個目錄下,文件名為服務地址。
standalone
如果把這個 skynet 進程作為主進程啟動(skynet 可以由分布在多臺機器上的多個進程構成網絡),那麽需要配置standalone
這一項,表示這個進程是主節點,它需要開啟一個控制中心,監聽一個端口,讓其它節點接入。 - master 指定 skynet 控制中心的地址和端口,如果你配置了
standalone
項,那麽這一項通常和standalone
相同。 - address 當前 skynet 節點的地址和端口,方便其它節點和它組網。註:即使你只使用一個節點,也需要開啟控制中心,並額外配置這個節點的地址和端口。
- harbor 可以是
1-255
間的任意整數。一個 skynet 網絡最多支持 255 個節點。每個節點有必須有一個唯一的編號。
如果harbor
為 0 ,skynet 工作在單節點模式下。此時master
和address
以及standalone
都不必設置。 - start 這是
bootstrap
最後一個環節將啟動的 lua 服務,也就是你定制的 skynet 節點的主程序。默認為main
,即啟動main.lua
這個腳本。這個 lua 服務的路徑由下面的luaservice
指定。
- logger 它決定了 skynet 內建的
-
集群服務用到的配置項:
- cluster 它決定了集群配置文件的路徑。
-
lua 服務由 snlua 提供,它會查找一些配置項以加載 lua 代碼:
- lualoader 用哪一段 lua 代碼加載 lua 服務。通常配置為
lualib/loader.lua
,再由這段代碼解析服務名稱,進一步加載 lua 代碼。snlua 會將下面幾個配置項取出,放在初始化好的 lua 虛擬機的全局變量中。具體可參考實現。 - SERVICE_NAME 第一個參數,通常是服務名。
- LUA_PATH config 文件中配置的
lua_path
。 - LUA_CPATH config 文件中配置的
lua_cpath
。 - LUA_PRELOAD config 文件中配置的
preload
。 - LUA_SERVICE config 文件中配置的
luaservice
。 - luaservice lua 服務代碼所在的位置。可以配置多項,以 ; 分割。 如果在創建 lua 服務時,以一個目錄而不是單個文件提供,最終找到的路徑還會被添加到
package.path
中。比如,在編寫 lua 服務時,有時候會希望把該服務用到的庫也放到同一個目錄下。 - lua_path 將添加到
package.path
中的路徑,供require
調用。 - lua_cpath 將添加到
package.cpath
中的路徑,供require
調用。 - preload 在設置完 package 中的路徑後,加載 lua 服務代碼前,loader 會嘗試先運行一個
preload
制定的腳本,默認為空。 - snax 用
snax
框架編寫的服務的查找路徑。 - profile 默認為 true, 可以用來統計每個服務使用了多少 cpu 時間。在
DebugConsole
中可以查看。會對性能造成微弱的影響,設置為false
可以關閉這個統計。
另外,你也可以把一些配置選項配置在環境變量中。比如,你可以把 thread 配置在
SKYNET_THREAD
這個環境變量裏。你可以在config
文件中寫:thread=$SKYNET_THREAD
- 1
這樣,在 skynet 啟動時,就會用
SKYNET_THREAD
這個環境變量的值替換掉 config 中的$SKYNET_THREAD
了。 - lualoader 用哪一段 lua 代碼加載 lua 服務。通常配置為
2.啟動Skynet服務:
編譯完成後,查詢根目錄的文件列表,發現生成了一個 skynet
可執行文件:
linsh@ubuntu:/application/skynet$ ls
3rd HISTORY.md lualib platform.mk service-src test
cservice LICENSE lualib-src README.md skynet
examples luaclib Makefile service skynet-src
- 1
- 2
- 3
- 4
在skynet的根目錄運行以下指令 ./skynet examples/config
啟動skynet服務:
linsh@ubuntu:/application/skynet$ ./skynet examples/config
[:01000001] LAUNCH logger
[:01000002] LAUNCH snlua bootstrap
[:01000003] LAUNCH snlua launcher
[:01000004] LAUNCH snlua cmaster
[:01000004] master listen socket 0.0.0.0:2013
[:01000005] LAUNCH snlua cslave
[:01000005] slave connect to master 127.0.0.1:2013
[:01000004] connect from 127.0.0.1:34760 4
[:01000006] LAUNCH harbor 1 16777221
[:01000004] Harbor 1 (fd=4) report 127.0.0.1:2526
[:01000005] Waiting for 0 harbors
[:01000005] Shakehand ready
[:01000007] LAUNCH snlua datacenterd
[:01000008] LAUNCH snlua service_mgr
[:01000009] LAUNCH snlua main
[:01000009] Server start
[:0100000a] LAUNCH snlua protoloader
[:0100000b] LAUNCH snlua console
[:0100000c] LAUNCH snlua debug_console 8000
[:0100000c] Start debug console at 127.0.0.1:8000
[:0100000d] LAUNCH snlua simpledb
[:0100000e] LAUNCH snlua watchdog
[:0100000f] LAUNCH snlua gate
[:0100000f] Listen on 0.0.0.0:8888
[:01000009] Watchdog listen on 8888
[:01000009] KILL self
[:01000002] KILL self
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
其他資料:
- 雲風對Skynet的介紹視頻:
雲風:基於 Actor 模式的開源框架
參考:
- Skynet 設計綜述
- 雲風skynet服務端框架研究
- 雲風的 BLOG: Skynet 開源
- 雲風的 BLOG: Skynet 集群及 RPC
- skynet/wiki
- GettingStarted
- Config
Skynet服務器框架(一) Linux下的安裝和啟動