Skynet服務器框架(一)Linux下的安裝和啟動

引言:

一直都是從事客戶端的開發工作,最近抽了點時間想了解一下服務器開發的相關知識,一番博客瞎逛之后,發現了一個不錯的框架,云風大神的 skynet開源服務器框架,這不僅僅是針對于游戲服務器開發的框架,更是一個通用的服務器基礎框架。

Skynet簡介:

Skynet 主要工作是管理注冊服務,并開啟多線程協調服務之間的調用和通訊。

1.框架核心:

根據云風博客的描述,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 路徑下可以放置你需要的各種組件,比如:CJSONsqliteOpenSSL等。

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

假如執行正常,輸出如下:

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.
檢查連接... 完成。

假如還沒安裝git工具,可以通過以下指令安裝(我的操作系統是 Ubuntu14.04.4):

sudo apt-get install git

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操作系統)

3.源碼編譯:

  • 工具安裝:
    在編譯前還需要安裝兩個工具,不然會出現報錯:

  • 安裝autoconf

    sudo apt-get install autoconf
    

否則會報如下錯誤:

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
  • 安裝readline-devel

    sudo apt-get install libreadline-dev
    

否則會報如下錯誤:

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
  • 編譯操作:
    由于下載的是源碼,需要進過編譯才能運行,編譯過程就是:

  • 進入 clone 到本地的項目目錄,執行make指令編譯源碼:

    cd skynet
    sudo make linux
    

    假如編譯過程正常,編譯完成后如下:

    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'
    

4.錯誤集:

  • 錯誤一:
    由于系統時間設置錯誤,導致編譯進入死循環,報錯如下:
make[2]: *** 警告:文件“Makefile.in”的修改時間在將來1.8e+06

解決方案:

  • 假如當前系統時間是錯誤的,修正系統時間即可;

  • 假如是文件時間戳有誤,可以使用 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

解決方案:
選擇其他非共享的目錄進行安裝,例如直接復制:

sudo cp -r skynet /application/

啟動流程:

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"

不難看出,這個配置文件內存其實是一個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 工作在單節點模式下。此時 masteraddress 以及 standalone 都不必設置。

  • start 這是 bootstrap 最后一個環節將啟動的 lua 服務,也就是你定制的 skynet 節點的主程序。默認為 main ,即啟動 main.lua 這個腳本。這個 lua 服務的路徑由下面的 luaservice 指定。

  • 集群服務用到的配置項:

  • 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 制定的腳本,默認為空。

  • snaxsnax 框架編寫的服務的查找路徑。

  • profile 默認為 true, 可以用來統計每個服務使用了多少 cpu 時間。在 DebugConsole 中可以查看。會對性能造成微弱的影響,設置為 false 可以關閉這個統計。

另外,你也可以把一些配置選項配置在環境變量中。比如,你可以把 thread 配置在 SKYNET_THREAD 這個環境變量里。你可以在 config 文件中寫:

thread=$SKYNET_THREAD

這樣,在 skynet 啟動時,就會用 SKYNET_THREAD 這個環境變量的值替換掉 config 中的 $SKYNET_THREAD 了。

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

在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

其他資料:

參考:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容