skynet爲了簡化服務的編寫,推出了snax框架,源碼裏也有一個例子pingserver。這是snax原創文章的第一篇,因此先就分析snax框架裏的interface.lua源碼,它的實現應用了一個閉包中的upvalue注入技巧。bootstrap
凡是框架都得遵循框架的約定,snax有兩個大的約定,一是約定了一組預置的接口init/exit/hotfix;二是accept/response這兩組用來編寫服務的接口。本文,並不涉及這些,而是談accept/response是如何注入給snax服務的。數組
snax框架裏new一個服務的流程以下,最終調用到snax_inteface裏的interface.lua,該文件裏只有一個函數:瀏覽器
snax.newservice->snax.rawnewservice->snax.interface->snax_interface閉包
local temp_global = {} local env = setmetatable({} , { __index = temp_global }) local func = {} local system = { "init", "exit", "hotfix" } do for k, v in ipairs(system) do system[v] = k func[k] = { k , "system", v } end end
首先看上面這斷代碼,func是函數最終要返回的對象,它 是一個表。在do語句塊裏,func最終會被擴展爲添加了三個system接口的數組。接下來就是兩行代碼,框架
env.accept = func_id(func, "accept") env.response = func_id(func, "response")
func_id再作什麼呢,func_id返回的是一個空表,這個表上設置了一個元表,並重寫了元表的__newindex,也就是對該表新鍵賦值時會觸發的操做,env.accept/env.response都是一個帶元表的空表,這裏也就是用戶編寫snax服務須要用到的兩個表,看一下pingserver.luaide
function accept.hello() lock(function() i = i + 1 print (i, hello) end) end function response.ping(hello) skynet.sleep(100) return hello end
只有在snax框架裏,上述代碼纔會工做,不然skynet框架會報錯,由於找不不response與accetp對象。那變量是怎麼注入的呢函數
do local path = skynet.getenv "snax" local errlist = {} for pat in string.gmatch(path,"[^;]+") do local filename = string.gsub(pat, "?", name) local f , err = loader(filename, "bt", G) if f then pattern = pat mainfunc = f break else table.insert(errlist, err) end end if mainfunc == nil then error(table.concat(errlist, "\n")) end end mainfunc()
上面這段代碼,首先從配置snax路徑裏去查找到指定的編寫的snax服務,找到以後,就用loader去加載文件,這個loader默認狀況下是是lua API loadfile測試
loader = loader or loadfile
變量注入依靠的就是這個loadfile,其實呢,我也是從這份代碼裏首次看到loadfile這樣使用,謝謝雲風。通常就只用到第一個參數,第二個參數都沒看到用。ui
看一下loadfile的C實現lua
static int luaB_loadfile (lua_State *L) { const char *fname = luaL_optstring(L, 1, NULL); const char *mode = luaL_optstring(L, 2, NULL); int env = (!lua_isnone(L, 3) ? 3 : 0); /* 'env' index or 0 if no 'env' */ int status = luaL_loadfilex(L, fname, mode); return load_aux(L, status, env); } static int load_aux (lua_State *L, int status, int envidx) { if (status == LUA_OK) { if (envidx != 0) { /* 'env' parameter? */ lua_pushvalue(L, envidx); /* environment for loaded function */ if (!lua_setupvalue(L, -2, 1)) /* set it as 1st upvalue */ lua_pop(L, 1); /* remove 'env' if not used by previous call */ } return 1; } else { /* error (message is on top of the stack) */ lua_pushnil(L); lua_insert(L, -2); /* put before error message */ return 2; /* return nil plus error message */ } }
首先,luaB_loadfile會看一下env是否設置,而後調用load_aux,這裏若是loadfile的第三個參數正確設置就會調用lua_setupvalue,把env設置爲upvalue。
當成功loadfile以後的mainfunc,會當即執行,這時pingserver就能正確找到變量upvalue中的response與accept
最後,skynet源碼裏沒有啓動pingserver的配置文件,下面給一個:
config_snax
root = "./" thread = 8 logger = nil logpath = "." harbor = 1 address = "127.0.0.1:2526" master = "127.0.0.1:2013" start = "simplesnax" -- 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" -- preload = "./examples/preload.lua" -- run preload.lua before every lua service run snax = root.."examples/?.lua;"..root.."test/?.lua" cpath = root.."cservice/?.so" -- daemon = "./skynet.pid"
start的simplesnax:
local skynet = require "skynet" local snax = require "snax" skynet.start(function() local ps = snax.newservice("pingserver", "hello world") end)
最後測試文件:config_ps
thread = 8 mqueue = 256 cpath = "./cservice/?.so" logger = nil harbor = 2 address = "127.0.0.1:2527" master = "127.0.0.1:2013" start = "testping" luaservice ="./service/?.lua;./test/?.lua;./examples/?.lua" snax = "./examples/?.lua;./test/?.lua"
測試結果:
最後,若是你不想去下源碼,只想立刻嘗試一下,點擊下面的地址,便可在CloudfusionIDE裏在線閱讀源碼,甚至運行(Google/火狐瀏覽器):http://cloudfusion.cc/ide/cloudfusion/skynet
轉載請註明出處