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
  • 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
    • 安裝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 指定。
  • 集羣服務用到的配置項:

    • 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 了。

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

其他資料:

參考:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章