杭州 手機(jī)網(wǎng)站免費(fèi)搭建網(wǎng)站的軟件
其實(shí)從C層的代碼看,skynet沒有太出彩的地方(也仍然很優(yōu)秀),有些人草草瞄了幾眼C層的代碼,就斷定skynet很一般:凡是有經(jīng)驗(yàn)的服務(wù)器程序,用個(gè)什么東西分分鐘就搭出一個(gè)skynet之類的話
。其實(shí)他們不知道,skynet對Lua的封裝才是最好的部分,云風(fēng)前輩對Lua的理解當(dāng)屬國內(nèi)最頂尖的那幾個(gè)。
這一部分非常細(xì)節(jié),也非常難懂,不想了解的人估計(jì)不會看,了解了的人大概也已經(jīng)了解,所以就當(dāng)是自己的備忘錄
skynet提供了一個(gè)snlua模塊,每創(chuàng)建一個(gè)snlua類型的服務(wù),snlua就創(chuàng)建一個(gè)Lua虛擬機(jī),這使得lua服務(wù)之間完全隔離,唯一的通訊方式就是通過skynet的消息機(jī)制,每一個(gè)消息都在一個(gè)lua協(xié)程處理,當(dāng)消息處理完畢,或中間向其他服務(wù)發(fā)送消息,協(xié)程可能會掛起,等其他服務(wù)回應(yīng)這個(gè)消息
時(shí),協(xié)程才重新喚醒,這種方式使得異步代碼像同步一樣執(zhí)行,不用寫一大堆回調(diào)函數(shù)。
有了Lua類型的服務(wù),skynet是不是有點(diǎn)像操作系統(tǒng)的概念,skynet的C層代碼像操作系統(tǒng)內(nèi)核,負(fù)責(zé)服務(wù)的調(diào)度,而Lua服務(wù)很像進(jìn)程,有自己獨(dú)立的空間(虛擬機(jī)獨(dú)立),Lua協(xié)程則像系統(tǒng)線程,只不過區(qū)別在于線程是真正的并發(fā),協(xié)程是協(xié)作式的并發(fā)。每個(gè)Lua服務(wù)可以保證,同一時(shí)刻,只有一個(gè)線程在執(zhí)行Lua協(xié)程,所以我們完全不必?fù)?dān)心線程同步的問題,當(dāng)我們在編寫Lua服務(wù)時(shí),就把它當(dāng)成一個(gè)單線程一樣。
bootstrap
回頭看skynet_start.c,在skynet_start函數(shù)中,有這樣的代碼片段:
// 創(chuàng)建logger服務(wù)
struct skynet_context *ctx = skynet_context_new(config->logservice, config->logger);
// 創(chuàng)建引導(dǎo)服務(wù)
bootstrap(ctx, config->bootstrap);
上面是創(chuàng)建一個(gè)Logger服務(wù),config->logger是保存日志的路徑,如果為NULL則輸出到stdout,logger服務(wù)會調(diào)用skynet_command(ctx, "REG", ".logger")注冊名字(logger),這樣就可以方便地用logger找到它的句柄,從而向他發(fā)送日志。
bootstrap函數(shù)負(fù)責(zé)創(chuàng)建一個(gè)lua服務(wù),config->bootstrap的內(nèi)容默認(rèn)是snlua bootstrap
,從C底層來看,它是一個(gè)snlua類型的服務(wù),bootstrap是lua服務(wù)執(zhí)行的腳本,從字面上看是一個(gè)引導(dǎo)服務(wù)。
bootstrap函數(shù)如下:
static void
bootstrap(struct skynet_context * logger, const char * cmdline) {// 啟動一個(gè)引導(dǎo)服務(wù),默認(rèn)情況下name為snlua,args為bootstrap.lua這個(gè)腳本int sz = strlen(cmdline);char name[sz+1];char args[sz+1];sscanf(cmdline, "%s %s", name, args);// 創(chuàng)建服務(wù)struct skynet_context *ctx = skynet_context_new(name, args);... ...
}
看過skynet總體架構(gòu),很清楚的知道這是創(chuàng)建一個(gè)snlua的服務(wù),bootstrap為作這個(gè)服務(wù)的參數(shù)傳過去。
snlua服務(wù)
創(chuàng)建snlua服務(wù)后,模塊中的snlua_create首先得到調(diào)用,它做的事情也非常簡單:
struct snlua *
snlua_create(void) {// 初始化snlua結(jié)構(gòu)struct snlua * l = skynet_malloc(sizeof(*l));memset(l,0,sizeof(*l));l->mem_report = MEMORY_WARNING_REPORT;l->mem_limit = 0;// 創(chuàng)建Lua狀態(tài)機(jī)l->L = lua_newstate(lalloc, l);return l;
}
就是創(chuàng)建一個(gè)snlua結(jié)構(gòu),創(chuàng)建一個(gè)Lua虛擬機(jī),內(nèi)存分配指定的是lalloc,目的是為了監(jiān)控Lua分配的內(nèi)存。MEMORY_WARNING_REPORT為Lua服務(wù)的內(nèi)存閥值,超過該值,會報(bào)警。
snlua結(jié)構(gòu)如下:
struct snlua {lua_State * L; // Lua狀態(tài)機(jī)struct skynet_context * ctx; // 關(guān)聯(lián)的skynet服務(wù)size_t mem; // Lua使用的內(nèi)存,在lalloc記錄size_t mem_report; // 內(nèi)存預(yù)警,當(dāng)達(dá)到閥值會打一條日志,然后閥值翻倍size_t mem_limit; // 內(nèi)存限制
};
創(chuàng)建snlua實(shí)例之后,調(diào)用snlua_init:
int
snlua_init(struct snlua *l, struct skynet_context *ctx, const char * args) {int sz = strlen(args);char * tmp = skynet_malloc(sz);memcpy(tmp, args, sz);// 指定回調(diào)函數(shù)為launch_cbskynet_callback(ctx, l , launch_cb);// 取本服務(wù)的句柄const char * self = skynet_command(ctx, "REG", NULL);uint32_t handle_id = strtoul(self+1, NULL, 16);// it must be first message:// 第一個(gè)消息在launch_cb處理,見函數(shù)skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);return 0;
}
- 首先調(diào)用skynet_callback指定消息回調(diào)函數(shù),指定為launch_cb。
- 然后取得本服務(wù)關(guān)聯(lián)的句柄,調(diào)用skynet_command這個(gè)API獲得。
- 最后向本服務(wù)發(fā)送第1個(gè)消息,打上PTYPE_TAG_DONTCOPY標(biāo)記,這表示skynet內(nèi)部不會重新分配內(nèi)存拷貝tmp。
第1條消息,使得launch_cb被回調(diào),launch_cb調(diào)用skynet_callback把回調(diào)函數(shù)去掉,然后調(diào)用init_cb,最后的邏輯都在init_cb里,前面既然把回調(diào)去掉了,那么肯定在某個(gè)地方會把回調(diào)函數(shù)加上(后面會看到)。
init_cb做的事情:
- 設(shè)置Lua的全局變量:
- LUA_PATH:Lua搜索路徑,在config.lua_path指定。
- LUA_CPATH:C模塊的搜索路徑,在config.lua_cpath指定。
- LUA_SERVICE:Lua服務(wù)的搜索路徑,在config.luaservice指定。
- LUA_PRELOAD:預(yù)加載腳本