在游戲服務器開發領域,高效、穩定且易于擴展的框架一直是開發者追求的目標。Skynet 作為一款輕量級、高性能的分布式游戲服務器框架,憑借其獨特的設計理念和強大的功能,贏得了眾多開發者的青睞
一.Skynet底層架構支持
1.Actor
erlang 從語言層面支持 actor 并發模型,并發實體是 actor (在 skynet 中稱之為 服務 ); skynet 采用 c + lua 來實現 actor 并發模型;底層也是通過采用多少個核心開啟多少個內核線程來充分利用多核;
?
有消息的 actor 為活躍的 actor,沒有消息為非活躍的 actor;?
?2.config
我們要配置config文件來支持skynet的運行
thread=8 --8個工作線程
logger=nil --不打日志
harbor=0
start="main" --main文件作為入口函數
lua_path="./skynet/lualib/?.lua;./skynet/lualib/?/init.lua;"
-- lua 抽象進程
luaservice="./skynet/service/?.lua;./app/?.lua;"
lualoader="./skynet/lualib/loader.lua"
cpath="./skynet/cservice/?.so"
lua_cpath="./skynet/luaclib/?.so"
?3.core-eg
我們通過一個網絡消息的代碼案例來看actor是怎么運行的
local skynet = require "skynet"
local socket = require "skynet.socket"skynet.start(function()local listenfd = socket.listen("192.168.217.148",8888)socket.start(listenfd,function (clientfd,addr)print("recive a client:",clientfd,addr)end)skynet.timeout(100,function()print("after 1s ,do here")end)print("hello skynet")-- local slave = skynet.newservice("slave")-- local response = skynet.call(slave,"lua","ping")--[[main -> ping ->slave //協程掛起slave -> pong ->main //協程喚醒c/c++ 異步流程 callbcak 協程來實現通過協程的方式消除異步產生的回調我們就能寫同步的代碼]]-- print(response)
end)
?
skynet.start
?是服務的入口點,Skynet 會為這個服務創建一個獨立的 Actor服務啟動時執行的代碼(如打印 "hello skynet")在 Actor 初始化階段同步執行
socket.listen
?和?socket.start
?注冊了一個網絡監聽事件當有新客戶端連接時,Skynet 會將這個事件封裝成消息發送給當前服務 Actor
服務 Actor 在處理消息時執行回調函數(打印客戶端信息)
skynet.timeout
?注冊了一個定時器事件Skynet 的定時器系統會在指定時間后向服務 Actor 發送一個超時消息
服務 Actor 收到超時消息后執行對應的回調函數
?
main
local skynet = require "skynet"
local socket = require "skynet.socket"skynet.start(function()local slave = skynet.newservice("slave")local response = skynet.call(slave,"lua","ping")--[[main -> ping ->slave //協程掛起slave -> pong ->main //協程喚醒c/c++ 異步流程 callbcak 協程來實現通過協程的方式消除異步產生的回調我們就能寫同步的代碼]]print(response)
end)
slave
local skynet = require "skynet"local CMD = {}function CMD.ping()skynet.retpack("pong")
endskynet.start(function()
skynet.dispatch("lua",function(session,source,cmd,...)local func = assert(CMD[cmd])func(...)
end)
end)
?我們上述代碼通過callback回調的方式來驅動actor運行
?4.公平調度
?
actor 是抽象的用戶態進程,相對于 linux 內核 ,有進程調度,那么 skynet 也要實現 actor 調度1. 將活躍的 actor 通過 全局隊列 組織起來; actor 當中的 消息隊列 有消息就是活躍的 actor ;2. 線程池去 全局隊列中取出 actor 的消息隊列,接著運行 actor ;
二.lua/c 接口編程
skynet 、 openresty 都是深度使用 lua 語言的典范;學習 lua 不僅僅要學習基本用法,還要學會使用 c 與 lua 交互,這樣才學會了 lua 作為膠水語言的精髓;
?
?1.虛擬棧
①棧中只能存放 lua 類型的值,如果想用 c 的類型存儲在棧中,需要將 c 類型轉換為 lua 類型;②lua 調用 c 的函數都得到一個 新 的棧,獨立于之前的棧;③c 調用 lua ,每一個協程都有一個棧;④c? 創建虛擬機時,伴隨創建了一個主協程,默認創建一個虛擬棧;⑤無論何時 Lua 調用 C , 它都只保證至少有 LUA_MINSTACK 這么多的堆棧空間可以使用。⑥LUA_MINSTACK 一般被定義為 20 , 因此,只要你不是不斷的把數據壓棧, 通常你不用關心堆棧 大小。
?2.C調用lua接口
#include <stdio.h>
#include <stdlib.h>#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>//lua嵌入在C語言里面的好處--->熱更新 不需要重新編譯lua代碼 重啟即可更新or文件變化重新加載/** 調用Lua全局函數,向其傳遞一個整數值1* L: Lua虛擬機指針* funcname: 要調用的Lua函數名稱*/
static void
call_func_0(lua_State *L, const char* funcname)
{// 從全局環境中獲取指定名稱的函數lua_getglobal(L, funcname);//向棧頂壓入整數參數1lua_pushinteger(L, 1);// 調用函數,1個參數,0個返回值lua_call(L, 1, 0);//C調lua必須維護棧的平衡 eg:--->lua_pop(L);
}int main(int argc, char** argv)
{//創建新的lua虛擬機lua_State *L = luaL_newstate();//打開lua標準庫luaL_openlibs(L);if (argc > 1) {lua_pushboolean(L, 1);lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");if ( LUA_OK != luaL_dofile(L, argv[1]) ) {const char* err = lua_tostring(L, -1);fprintf(stderr, "err:\t%s\n", err);return 1;}// 依次調用Lua腳本中的Init、Loop、Release函數call_func_0(L, "Init");call_func_0(L, "Loop");call_func_0(L, "Release");lua_close(L);return 0;}return 0;
}
package.cpath = "luaclib/?.so"
local so = require "tbl.c"--a=1
--全局可見--local a =1
--當前文件可見-- 加了local相當于C/C++中的staticfunction Init(args)print("call [init] function", args)
endfunction Loop()print("call [loop] function")for k, v in ipairs({1,2,3,4,5}) do--so.echo(v) -- 等于 調用 5 次 因為每次調用都是一個新的虛擬棧,所以沒有必要維護 --->C調用lua然后調Cprint("value = " .. v) --->C調用luaendreturn 1
endfunction Release()print("call [release] function")
end
c->lua每個協程一個棧 我們需要維護棧的空間 彈出不需要的棧空間
?3.C調用lua接口-->lua再調用C接口
#include <stdio.h>
#include <stdlib.h>#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>//lua嵌入在C語言里面的好處--->熱更新 不需要重新編譯lua代碼 重啟即可更新or文件變化重新加載/** 調用Lua全局函數,向其傳遞一個整數值1* L: Lua虛擬機指針* funcname: 要調用的Lua函數名稱*/
static void
call_func_0(lua_State *L, const char* funcname)
{// 從全局環境中獲取指定名稱的函數lua_getglobal(L, funcname);//向棧頂壓入整數參數1lua_pushinteger(L, 1);// 調用函數,1個參數,0個返回值lua_call(L, 1, 0);//C調lua必須維護棧的平衡 eg:--->lua_pop(L);
}int main(int argc, char** argv)
{//創建新的lua虛擬機lua_State *L = luaL_newstate();//打開lua標準庫luaL_openlibs(L);if (argc > 1) {lua_pushboolean(L, 1);lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");if ( LUA_OK != luaL_dofile(L, argv[1]) ) {const char* err = lua_tostring(L, -1);fprintf(stderr, "err:\t%s\n", err);return 1;}// 依次調用Lua腳本中的Init、Loop、Release函數call_func_0(L, "Init");call_func_0(L, "Loop");call_func_0(L, "Release");lua_close(L);return 0;}return 0;
}
package.cpath = "luaclib/?.so"
local so = require "tbl.c"--a=1
--全局可見--local a =1
--當前文件可見-- 加了local相當于C/C++中的staticfunction Init(args)print("call [init] function", args)
endfunction Loop()print("call [loop] function")for k, v in ipairs({1,2,3,4,5}) doso.echo(v) -- 等于 調用 5 次 因為每次調用都是一個新的虛擬棧,所以沒有必要維護 --->C調用lua然后調C--print("value = " .. v) --->C調用luaendreturn 1
endfunction Release()print("call [release] function")
end
#include <lua.h>#include <lauxlib.h>#include <lualib.h>#include <stdio.h>static int
lecho (lua_State *L) {const char* str = lua_tostring(L, -1);fprintf(stdout, "%s\n", str);return 0;
}static const luaL_Reg l[] = {// 導出給lua使用數組{"echo", lecho},{NULL, NULL},
};int
luaopen_tbl_c(lua_State *L) { // local tbl = require "tbl.c"// 創建一張新的表,并預分配足夠保存下數組 l 內容的空間// luaL_newlibtable(L, l);// luaL_setfuncs(L, l, 0);luaL_newlib(L, l);return 1;
}
4.運行lua/?src/lua 可執行程序讓lua調用C接口
①
#include <lua.h>#include <lauxlib.h>#include <lualib.h>#include <stdio.h>static int
lecho (lua_State *L) {const char* str = lua_tostring(L, -1);fprintf(stdout, "%s\n", str);return 0;
}static const luaL_Reg l[] = {// 導出給lua使用數組{"echo", lecho},{NULL, NULL},
};int
luaopen_tbl_c(lua_State *L) { // local tbl = require "tbl.c"// 創建一張新的表,并預分配足夠保存下數組 l 內容的空間// luaL_newlibtable(L, l);// luaL_setfuncs(L, l, 0);luaL_newlib(L, l);return 1;
}
package.cpath = "luaclib/?.so" --c庫的路徑local so = require "tbl.c"so.echo("hello world") -- 新的虛擬棧
so.echo("hello world1")-- 新的虛擬棧
so.echo("hello world2")-- 新的虛擬棧--[[1. c調用lua c有多個協程 每個協程一個虛擬棧2. lua調用c 每次調用都有一個虛擬棧
]]
②
#include <lua.h>#include <lauxlib.h>#include <lualib.h>#include <stdio.h>// 閉包實現: 函數 + 上值 luaL_setfuncs
// lua_upvalueindex(1)
// lua_upvalueindex(2)
static int
lecho (lua_State *L) {lua_Integer n = lua_tointeger(L, lua_upvalueindex(1));n++;const char* str = lua_tostring(L, -1);fprintf(stdout, "[n=%lld]---%s\n", n, str);lua_pushinteger(L, n);lua_replace(L, lua_upvalueindex(1));return 0;
}static const luaL_Reg l[] = {{"echo", lecho},{NULL, NULL},
};int
luaopen_uv_c(lua_State *L) { // local tbl = require "tbl.c"luaL_newlibtable(L, l);// 1lua_pushinteger(L, 0);// 2luaL_setfuncs(L, l, 1);// 上值// luaL_newlib(L, l);return 1;
}
?
package.cpath = "luaclib/?.so"local so = require "uv.c"so.echo("hello world1")
so.echo("hello world2")
so.echo("hello world3")
so.echo("hello world4")
so.echo("hello world5")
so.echo("hello world6")
so.echo("hello world7")
so.echo("hello world8")
so.echo("hello world9")--./lua/src/lua test-uv.lua
③
#include <lua.h>#include <lauxlib.h>#include <lualib.h>#include <stdio.h>#include <stdlib.h>#include <string.h>struct log {int count;
};static int
lagain(lua_State *L) {struct log *p = (struct log *)luaL_checkudata(L, 1, "mk.ud.log");lua_getuservalue(L, -1);const char* str = lua_tostring(L, -1);fprintf(stdout, "ud[n=%d]----%s\n", p->count, str);return 0;
}static int
lecho(lua_State *L) {struct log *p = (struct log *)luaL_checkudata(L, 1, "mk.ud.log");const char* str = lua_tostring(L, -1);p->count++;lua_setuservalue(L, -2);fprintf(stdout, "ud[n=%d]----%s\n", p->count, str);return 0;
}static int
lnew (lua_State *L) {struct log *q = (struct log*)lua_newuserdata(L, sizeof(struct log));q->count = 0;lua_pushstring(L, "");lua_setuservalue(L, -2);if (luaL_newmetatable(L, "mk.ud.log")) {luaL_Reg m[] = {{"echo", lecho},{"again", lagain},{NULL, NULL},};luaL_newlib(L, m);lua_setfield(L, -2, "__index");lua_setmetatable(L, -2);}return 1;
}static const luaL_Reg l[] = {{"new", lnew},{NULL, NULL},
};int
luaopen_ud_c(lua_State *L) {luaL_newlib(L, l);return 1;
}
?
package.cpath = "luaclib/?.so"local so = require "ud.c"local ud = so.new()ud:echo("hello world1")
ud:again()
ud:echo("hello world2")
ud:again()
ud:echo("hello world3")
ud:again()
ud:echo("hello world4")
ud:again()
ud:echo("hello world5")
ud:again()
ud:echo("hello world6")
ud:again()
ud:echo("hello world7")
ud:again()
ud:echo("hello world8")
ud:again()
ud:echo("hello world9")
ud:again()--./lua/src/lua test-ud.lua
lua->c不需要維護棧空間 因為每次調用都會生成一個新棧
?