服务端Skynet(三)——启动lua服务
admin
2024-01-28 08:32:29
0

服务端Skynet(三)——启动lua服务

文章目录

  • 服务端Skynet(三)——启动lua服务
    • 1、lua创建流程
    • 2、C语言流程
    • 3、调回到lua
    • 4、example服务案例

参考文献

skynet设计综述

skynet源码赏析

在源码浅析和消息调度机制两篇文章中基本上了解了skynet 中 服务 与 消息调度 相关的理论基础。但是没有提及服务是这么注册到skynet_modules管理模块的,服务运行在什么环境。现在通过分析lua层创建服务的流程理解一下。

1、lua创建流程

--[[
先总结一下lua部分创建服务的流程:
newservice --> skynet.call.launcher --> .launcher=skynet.launch(“snlua”, “launcher”) --> skynet.core.command(“LAUNCH”, “snlua launcher”)
]]

​ 每个skynet进程在启动时,都会启动一个lua层的launcher服务,该服务主要负责skynet运作期间,服务的创建工作。我们在lua层创建一个lua层服务时,通常会调用skynet.newservice函数:

-- skynet.lua
function skynet.newservice(name, ...)--发送消息给launcher服务,告诉launcher服务,要去创建一个snlua的c服务,并且绑定一个lua_State ,该lua_State运行名称为name的lua脚本(这个脚本是入口)return skynet.call(".launcher", "lua" , "LAUNCH", "snlua", name, ...)
end-- launcher.lua
local function launch_service(service, ...)--将c服务名称、脚本名称和参数,拼成一个字符串,并下传给c层local param = table.concat({...}, " ")local inst = skynet.launch(service, param)local response = skynet.response()if inst thenservices[inst] = service .. " " .. paraminstance[inst] = responseelseresponse(false)returnendreturn inst
endfunction command.LAUNCH(_, service, ...)launch_service(service, ...)return NORET
end

这里实质是调用另外一个服务(.launcher)完成skynet服务的创建。 关于.launcher:

--[[bootstrap.lua skynet服务的启动入口 在这里调用了skynet.launch,启动了一个launcher服务 具体的启动流程:在传入config后 -> skynet_start(&config) -> bootstrap(ctx, config->bootstrap) -> 启动调用bootstrap.lua(创建launcher服务)后续服务的创建都可以交给.launcher 服务进行为什么要用另一个服务创建新服务? 主要目的是为了方便管理所有服务,比如统计,gc,杀掉服务等。
]]
local launcher = assert(skynet.launch("snlua","launcher"))
skynet.name(".launcher", launcher)

skynet.launch的实现如下:

--manager.lua
local skynet = require "skynet"  
local c = require "skynet.core" 	--C语言模块  command方法function skynet.launch(...)local addr = c.command("LAUNCH", table.concat({...}," "))if addr thenreturn tonumber("0x" .. string.sub(addr , 2))end
end

2、C语言流程

/*总结一下C语言的处理流程:  skynet.core.command --> lcommand --> skynet_command --> cmd_launch --> skynet_context_new --> snlua_create --> snlua_init --> 加载loader.lua
*/

通过上面lua流程 现在跑到了skynet.core.command 看一下对应的C代码:

//lua_skynet.c   lcommand 对应lua中command
static int lcommand(lua_State *L) {struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));const char * cmd = luaL_checkstring(L,1);const char * result;const char * parm = NULL;if (lua_gettop(L) == 2) {parm = luaL_checkstring(L,2);}result = skynet_command(context, cmd, parm);//cmd:LAUNCH parm:snlua launcherif (result) {lua_pushstring(L, result);return 1;}return 0;
}/*lcommand 核心是调了skynet_command  看一下skynet_command的代码:
*///skynet-server.c
/*static struct command_func cmd_funcs[] = {{ "TIMEOUT", cmd_timeout },{ "REG", cmd_reg },{ "QUERY", cmd_query },{ "NAME", cmd_name },{ "EXIT", cmd_exit },{ "KILL", cmd_kill },{ "LAUNCH", cmd_launch },{ "GETENV", cmd_getenv },{ "SETENV", cmd_setenv },{ "STARTTIME", cmd_starttime },{ "ABORT", cmd_abort },{ "MONITOR", cmd_monitor },{ "STAT", cmd_stat },{ "LOGON", cmd_logon },{ "LOGOFF", cmd_logoff },{ "SIGNAL", cmd_signal },{ NULL, NULL },};
*/
//通过上文 这里cmd初始是 LAUNCH
const char * skynet_command(struct skynet_context * context, const char * cmd , const char * param) {struct command_func * method = &cmd_funcs[0];while(method->name) {if (strcmp(cmd, method->name) == 0) {//通过注册命令 选择对应方法 return method->func(context, param);}++method;}return NULL;
}//LAUNCH
static const char * cmd_launch(struct skynet_context * context, const char * param) {size_t sz = strlen(param);char tmp[sz+1];strcpy(tmp,param);char * args = tmp;char * mod = strsep(&args, " \t\r\n");args = strsep(&args, "\r\n");//snlua_create 这里mod:snlua args:snlua launcherstruct skynet_context * inst = skynet_context_new(mod,args);// 实例化上下文  if (inst == NULL) {return NULL;} else {id_to_hex(context->result, inst->handle);return context->result;}
}// skynet_context_new  
struct skynet_context * skynet_context_new(const char * name, const char *param) {  struct skynet_module * mod = skynet_module_query(name);if (mod == NULL)return NULL;//根据参数实例化一个模块/*skynet_module_instance_create 这里会调用模块的create方法例如snlua 的snlua_create   //service_snlua.clua_newstate(lalloc, l);  //snlua_create  创建了一个lua_Statesnlua是lua的一个沙盒服务,保证了各个lua服务之间是隔离的 */void *inst = skynet_module_instance_create(mod); // if (inst == NULL)return NULL;//初始化并注册 ctx 创建该服务的消息队列struct skynet_context * ctx = skynet_malloc(sizeof(*ctx));CHECKCALLING_INIT(ctx)ctx->mod = mod;ctx->instance = inst;ATOM_INIT(&ctx->ref , 2);ctx->cb = NULL;ctx->cb_ud = NULL;ctx->session_id = 0;ATOM_INIT(&ctx->logfile, (uintptr_t)NULL);ctx->init = false;ctx->endless = false;ctx->cpu_cost = 0;ctx->cpu_start = 0;ctx->message_count = 0;ctx->profile = G_NODE.profile;// Should set to 0 first to avoid skynet_handle_retireall get an uninitialized handlectx->handle = 0;	ctx->handle = skynet_handle_register(ctx);struct message_queue * queue = ctx->queue = skynet_mq_create(ctx->handle);// init function maybe use ctx->handle, so it must init at lastcontext_inc();CHECKCALLING_BEGIN(ctx)//初始化实例的模块int r = skynet_module_instance_init(mod, inst, ctx, param);CHECKCALLING_END(ctx)//把此服务的消息队列加入到全局消息队列if (r == 0) {struct skynet_context * ret = skynet_context_release(ctx);if (ret) {ctx->init = true;}skynet_globalmq_push(queue);if (ret) {skynet_error(ret, "LAUNCH %s %s", name, param ? param : "");}return ret;} else {skynet_error(ctx, "FAILED launch %s", name);uint32_t handle = ctx->handle;skynet_context_release(ctx);skynet_handle_retire(handle);struct drop_t d = { handle };skynet_mq_release(queue, drop_message, &d);return NULL;}
}
}  

此时,我们就已经创建了一个snlua的c服务,在创建snlua服务的过程中,会对新的snlua服务进行初始化操作:

// service_snlua.c
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);skynet_callback(ctx, l , _launch);//完成注册以后,向自己发送了一个消息,本snlua服务在接收到消息以后,就会调用_launch函数//snlua服务的回调函数会被赋空值,并进行一次snlua绑定的lua_State的初始化const char * self = skynet_command(ctx, "REG", NULL);uint32_t handle_id = strtoul(self+1, NULL, 16);// it must be first messageskynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);return 0;
}// snlua服务的callback函数
static int _launch(struct skynet_context * context, void *ud, int type, int session, uint32_t source , const void * msg, size_t sz) {assert(type == 0 && session == 0);struct snlua *l = ud;skynet_callback(context, NULL, NULL);int err = _init(l, context, msg, sz);if (err) {skynet_command(context, "EXIT", NULL);}return 0;
}

初始化lua_state:

// service_snlua.c
static int _init(struct snlua *l, struct skynet_context *ctx, const char * args, size_t sz) {lua_State *L = l->L;//保存服务指针 以方便lua层调c使用l->ctx = ctx;//配置设置lua_gc(L, LUA_GCSTOP, 0);lua_pushboolean(L, 1);  /* signal for libraries to ignore env. vars. */  lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");					 luaL_openlibs(L);												   lua_pushlightuserdata(L, ctx);lua_setfield(L, LUA_REGISTRYINDEX, "skynet_context");luaL_requiref(L, "skynet.codecache", codecache , 0);lua_pop(L,1);//设置lua服务脚本的存放路径  c服务so库的存放路径  lualib的存放路径const char *path = optstring(ctx, "lua_path","./lualib/?.lua;./lualib/?/init.lua");		lua_pushstring(L, path);lua_setglobal(L, "LUA_PATH");const char *cpath = optstring(ctx, "lua_cpath","./luaclib/?.so");					  lua_pushstring(L, cpath);lua_setglobal(L, "LUA_CPATH");const char *service = optstring(ctx, "luaservice", "./service/?.lua");				  lua_pushstring(L, service);lua_setglobal(L, "LUA_SERVICE");const char *preload = skynet_command(ctx, "GETENV", "preload");lua_pushstring(L, preload);lua_setglobal(L, "LUA_PRELOAD");lua_pushcfunction(L, traceback);assert(lua_gettop(L) == 1);//------------------------------------------------------------调回lua---------------------------------------------------//lua_State会加载一个用于执行指定脚本的loader.lua脚本,并将参数传给这个脚本//(参数就是snlua服务绑定的lua脚本名称和传给这个脚本的参数拼起来的字符串,比如要启动一个名为scene的服务,那么对应的脚本名称就是scene.lua)const char * loader = optstring(ctx, "lualoader", "./lualib/loader.lua");// 加载loader模块代码int r = luaL_loadfile(L,loader);if (r != LUA_OK) {skynet_error(ctx, "Can't load %s : %s", loader, lua_tostring(L, -1));_report_launcher_error(ctx);return 1;}lua_pushlstring(L, args, sz);//把服务名等参数传入,执行loader模块代码,实际上是通过loader加载和执行服务代码 r = lua_pcall(L,1,0,1);//------------------------------------------------------------调回lua---------------------------------------------------if (r != LUA_OK) {skynet_error(ctx, "lua loader error : %s", lua_tostring(L, -1));_report_launcher_error(ctx);return 1;}lua_settop(L,0);lua_gc(L, LUA_GCRESTART, 0);return 0;
}

3、调回到lua

--[[loader.lua在沙盒snlua中,加载并执行lua程序,可能执行如下几件事情:定义消息回调函数注册lua类型的消息回调函数注册除了lua类型以外的其他类型消息处理协议调用skynet.start函数,也就是将skynet.dispatch_message函数注册为lua服务的回调函数,所有派发给该lua服务的消息,都会传给这个函数;以及在下一帧执行lua服务启动逻辑的启动函数]]
local args = {}
for word in string.gmatch(..., "%S+") dotable.insert(args, word)
endSERVICE_NAME = args[1]local main, patternlocal err = {}
for pat in string.gmatch(LUA_SERVICE, "([^;]+);*") dolocal filename = string.gsub(pat, "?", SERVICE_NAME)local f, msg = loadfile(filename)if not f thentable.insert(err, msg)elsepattern = patmain = fbreakend
endif not main thenerror(table.concat(err, "\n"))
endLUA_SERVICE = nil
package.path , LUA_PATH = LUA_PATH
package.cpath , LUA_CPATH = LUA_CPATHlocal service_path = string.match(pattern, "(.*/)[^/?]+$")if service_path thenservice_path = string.gsub(service_path, "?", args[1])package.path = service_path .. "?.lua;" .. package.pathSERVICE_PATH = service_path
elselocal p = string.match(pattern, "(.*/).+$")SERVICE_PATH = p
endif LUA_PRELOAD thenlocal f = assert(loadfile(LUA_PRELOAD))f(table.unpack(args))LUA_PRELOAD = nil
end_G.require = (require "skynet.require").requiremain(select(2, table.unpack(args)))

skynet的lua服务,有一个proto表用于存放不同的消息类型的消息处理协议,一个协议,一般包含以下内容

  • name:表示协议类型的字符串,如lua类型,其值则为”lua”
  • id:标识协议类型的整型值,类型有
local skynet = {-- read skynet.hPTYPE_TEXT = 0,PTYPE_RESPONSE = 1,PTYPE_MULTICAST = 2,PTYPE_CLIENT = 3,PTYPE_SYSTEM = 4,PTYPE_HARBOR = 5,PTYPE_SOCKET = 6,PTYPE_ERROR = 7,PTYPE_QUEUE = 8,	-- used in deprecated mqueue, use skynet.queue insteadPTYPE_DEBUG = 9,PTYPE_LUA = 10,PTYPE_SNAX = 11,
}
  • pack:发送消息时,对消息进行打包的函数
  • unpack:接收到消息时,先通过unpack函数,对消息进行解包后,再传给dispatch函数,最后实现消息回调
  • dispatch:消息队列里的指定类型的消息,最终会传到指定类型的dispatch函数来,这个函数一般是用户自己指定

4、example服务案例

现在通过启动一个example服务来举例说明,example服务的定义如下所示:

-- example.lua
local skynet = require "skynet"skynet.register_protocol {name = "text",id = skynet.PTYPE_TEXT,unpack = function (msg, sz)return skynet.tostring(msg, sz)end,dispatch = function (_, _, type, arg)skynet.error(arg)end
}local CMD = {}function CMD.do_something(...)-- TODO
endskynet.dispatch("lua", function(_,_, command, ...)local f = CMD[command]skynet.ret(skynet.pack(f(...)))
end)skynet.start(function()-- TODO
end)

启动一个example服务,意味着loader脚本,最终执行的那个脚本就是example.lua这个脚本,在执行这个脚本的过程中,首先和其他所有的lua服务一样,这个example服务需要调用skynet.lua里的api,因此,需要require一下skynet.lua这个脚本,而在require的过程中,就已经注册了几个回调消息处理协议:

-- skynet.lua...----- register protocol
dolocal REG = skynet.register_protocolREG {name = "lua",id = skynet.PTYPE_LUA,pack = skynet.pack,unpack = skynet.unpack,}REG {name = "response",id = skynet.PTYPE_RESPONSE,}REG {name = "error",id = skynet.PTYPE_ERROR,unpack = function(...) return ... end,dispatch = _error_dispatch,}
end...

这里事先注册了lua类型,response类型和error类型的消息处理协议,也就是说,一个lua服务至少保证lua类型、response类型和error类型默认有消息处理协议,而注册函数的流程就是将这个消息处理协议的结构存入proto表中,当一个lua服务接收到消息时,则会根据其消息类型,在proto表中找到对应的处理协议以后,调用该协议的unpack函数,将参数解包以后,再传给该协议的dispatch函数,最后达到驱动lua服务的目的:

-- skynet.lua
function skynet.register_protocol(class)local name = class.namelocal id = class.idassert(proto[name] == nil)assert(type(name) == "string" and type(id) == "number" and id >=0 and id <=255)proto[name] = classproto[id] = class
end

现在回到我们的example脚本,它注册了一个text类消息处理协议,然后为lua协议注册了一个回调函数,我们注意到,在skynet.lua这个脚本中,虽然有在proto表中注册lua类型协议(其中包括解包函数unpack),但是没有定义消费lua类型消息的回调函数dispatch,这个dispatch函数,需要用户自己定义,一般使用skynet.dispatch来完成

-- skynet.lua
function skynet.dispatch(typename, func)local p = proto[typename]if func thenlocal ret = p.dispatchp.dispatch = funcreturn retelsereturn p and p.dispatchend
end

exmaple脚本为lua协议,注册了一个lua层消息回调函数,后面example服务接收到的lua类型消息,都会被传到这个函数内
最后,example脚本执行了skynet.start函数,完成启动一个lua服务的最后工作

-- skynet.lua
function skynet.start(start_func)c.callback(skynet.dispatch_message)skynet.timeout(0, function()skynet.init_service(start_func)end)
end

这个函数,首先lua服务注册了一个lua层的消息回调函数,前面已经讨论过,一个c服务在消费次级消息队列的消息时,最终会调用callback函数,而这里做的工作则是,通过这个c层的callback函数,再转调lua层消息回调函数skynet.dispatch_message

// lua-skynet.c
static int
_cb(struct skynet_context * context, void * ud, int type, int session, uint32_t source, const void * msg, size_t sz) {lua_State *L = ud;int trace = 1;int r;int top = lua_gettop(L);if (top == 0) {lua_pushcfunction(L, traceback);lua_rawgetp(L, LUA_REGISTRYINDEX, _cb);} else {assert(top == 2);}lua_pushvalue(L,2);lua_pushinteger(L, type);lua_pushlightuserdata(L, (void *)msg);lua_pushinteger(L,sz);lua_pushinteger(L, session);lua_pushinteger(L, source);r = lua_pcall(L, 5, 0 , trace);...return 0;
}static int
_callback(lua_State *L) {struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));int forward = lua_toboolean(L, 2);luaL_checktype(L,1,LUA_TFUNCTION);lua_settop(L,1);lua_rawsetp(L, LUA_REGISTRYINDEX, _cb);lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD);lua_State *gL = lua_tothread(L,-1);if (forward) {skynet_callback(context, gL, forward_cb);} else {skynet_callback(context, gL, _cb);}return 0;
}

这里将snlua这个skynet_context的callback函数赋值为_cb,而_cb最终又会通过lua_State转调lua层的skynet.dispatch_message函数,也就是说,发送给snlua服务的消息,最终都是交给lua层去处理的
在完成lua层callback函数的注册以后,接下来就是执行lua服务的启动函数

-- skynet.lua
local function init_template(start)init_all()init_func = {}start()init_all()
endfunction skynet.pcall(start)return xpcall(init_template, debug.traceback, start)
endfunction skynet.init_service(start)local ok, err = skynet.pcall(start)if not ok thenskynet.error("init service failed: " .. tostring(err))skynet.send(".launcher","lua", "ERROR")skynet.exit()elseskynet.send(".launcher","lua", "LAUNCHOK")end
end

这里并没有立即执行这个start函数,而是故意放在了下一帧进行。到了目前这一步,整个example服务就被启动起来了,虽然他并没有执行什么逻辑,但是却展现了一个lua层服务完整的创建流程。

相关内容

热门资讯

帝国cms个人模板-帝国 CM... 哎呀,说到这个帝国CMS个人模板,我真是又爱又恨!你知道吗,每次我想给我的小破站换个新装,我就得跟这...
superstarsmt 苹果... 嘿,各位果粉们,今天咱们来聊聊那个让人心跳加速的游戏——SuperstarsMT苹果版!这可不是一般...
双宽带接入路由器:解决网速慢的... 哎呀呀,说到这个双宽带接入路由器,我这心里头啊,那叫一个激动!你们懂的,平时家里那网速,慢得跟蜗牛爬...
tabbaritem图片-探索... 哎呀,说到TabBarItem的图片,我这心头就涌上一股暖流。想当年,手机屏幕上那几个小图标,简直就...
分销王2代破解版-分销王 2 ... 嘿,大家好,我是个对科技产品有点痴迷的普通人。今天咱们聊聊那个在圈子里传得沸沸扬扬的“分销王2代破解...
分区助手558"-分... 哎呀,说到这个分区助手558,真是让人又爱又恨的小家伙!你知道吗,每次当我电脑里的文件乱得像一锅粥的...
sql验证身份证号码-如何用 ... 大家好!今天咱们来聊聊怎么用SQL来验证身份证号码。哎呀,这可不是件小事啊!身份证号码,那可是咱们的...
道路监控专用摄像机-城市守护者... 每天,当我们穿梭在繁忙的街道上,是否曾经留意过那些高高挂在电线杆上,或是静静藏在路灯下的摄像机?它们...
资源管理器增强-告别资源管理器... 大家好,我是你们的老朋友,一个对电脑界面有点强迫症的普通用户。今天,我要和大家聊聊那个每天都要见面的...
乡村道路施工设计方案:让坑洼小... 哎呀,说到咱这乡村的小路啊,真是让人又爱又恨!爱的是它们弯弯曲曲,像极了我们乡村人的生活,充满了故事...
arcscene三维显示dem... 嘿,大家好!今天我要给你们讲一个关于在ArcScene里玩转DEM的故事,这可不是一般的平面地图,而...
北京市朝阳区双桥中路-北京朝阳... 哎呀,说到咱们朝阳区的双桥中路,那真是个热闹非凡的地方啊!这条路,不长不短,却承载了无数人的欢声笑语...
怎么能查到身份证照片-身份证照... 哎呀妈呀,说到这个身份证照片,真是让人头疼!你说这年头,谁不想赶紧查到自己的身份证照片,可偏偏就是这...
香港身份证号码验证-香港身份证... 哎呀,说到香港身份证号码,你是不是觉得这就跟吃饭喝水一样平常?但你知道吗,这些看似简单的数字和字母,...
朝阳医院特需预约电话,关键时刻... 哎呀,说到朝阳医院的特需预约电话,我的心就扑通扑通跳个不停!你知道吗,这个电话号码对我来说,简直就是...
辽宁朝阳整形医院电话号码大揭秘... 嘿,朋友们!今天咱们聊点紧急的事儿——辽宁朝阳整形医院的电话号码!你可别小看这串数字,关键时刻能救急...
wine linux 64位下... 嘿,各位科技潮人们,今天咱们要聊的可是一件超级酷炫的事儿——WineLinux64位下载!是不是听起...
上海优化设计公司:创意狂人打造... 在上海这个国际大都市的心脏地带,有一群不按常理出牌的创意狂人,他们就是上海优化设计公司的灵魂。这里不...
北京重名查询成大麻烦,同名现象... 嘿,大家好!今天咱们聊聊北京这地儿的一个小麻烦——重名查询!你知道吗,在北京,同一个名字可能会出现在...
wine 64位下载-Wine... 大家好,今天咱们来聊聊这个让人又爱又恨的Wine64位下载!是不是有时候看着那些经典的老游戏,心里痒...