自上一章开始从main函数到启动log snlua 以及启动了monitor socket timer worker线程 并且对woker线程去message queue以及消息调度有了了解 下面将对snlua到lua进行剖析
一个服务启动过程是query(.so) create init release (singnal之后探讨)
下面先从snlua的snlua_create函数开始,因为skynet_context_new中先query模块之后就是create函数
struct snlua *
snlua_create(void) {
struct snlua * l = skynet_malloc(sizeof(*l));
memset(l,0,sizeof(*l));
l->mem_report = MEMORY_WARNING_REPORT;
l->mem_limit = 0;
l->L = lua_newstate(lalloc, l);//初始化lua虚拟机
l->activeL = NULL;
l->trap = 0;
return l;
}
在create中并没有特殊的 一般都是创建状态机
下面snlua_init进行剖析
int
snlua_init(struct snlua *l, struct skynet_context *ctx, const char * args) {
int sz = strlen(args);//申请args长度 args是bootstrap
char * tmp = skynet_malloc(sz);
memcpy(tmp, args, sz);
skynet_callback(ctx, l , launch_cb);//设置回调 回调为launch_cb 该回调会在消息队列执行到该handle时回调
const char * self = skynet_command(ctx, "REG", NULL);//发送消息
uint32_t handle_id = strtoul(self+1, NULL, 16);
// it must be first message
skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);//发送消息
return 0;
}
下面对skynet_command(ctx, “REG”, NULL)进行剖析
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 },//lua的launch会调用该命令 然后调用skynet_context_new 回调函数是调用callback函数进行配置
{ "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 },
};
const char *
skynet_command(struct skynet_context * context, const char * cmd , const char * param) {
struct command_func * method = &cmd_funcs[0];
while(method->name) {//根据cmd进行调用 cmd表见上
if (strcmp(cmd, method->name) == 0) {
return method->func(context, param);
}
++method;
}
return NULL;
}
下面对cmd_reg进行剖析
static const char *
cmd_reg(struct skynet_context * context, const char * param) {
if (param == NULL || param[0] == '\0') {
sprintf(context->result, ":%x", context->handle);//将context->handle以16进制转到context->result
return context->result;
} else if (param[0] == '.') {
return skynet_handle_namehandle(context->handle, param + 1);//待剖析
} else {
skynet_error(context, "Can't register global name %s in C", param);
return NULL;
}
}
下面对skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);剖析
int
skynet_send(struct skynet_context * context, uint32_t source, uint32_t destination , int type, int session, void * data, size_t sz) {
if ((sz & MESSAGE_TYPE_MASK) != sz) {//判断是否超长
skynet_error(context, "The message to %x is too large", destination);
if (type & PTYPE_TAG_DONTCOPY) {
skynet_free(data);
}
return -2;
}
_filter_args(context, type, &session, (void **)&data, &sz);
if (source == 0) {
source = context->handle;//source改为唯一handle
}
if (destination == 0) {
if (data) {
skynet_error(context, "Destination address can't be 0");
skynet_free(data);
return -1;
}
return session;
}
if (skynet_harbor_message_isremote(destination)) {
struct remote_message * rmsg = skynet_malloc(sizeof(*rmsg));
rmsg->destination.handle = destination;
rmsg->message = data;
rmsg->sz = sz & MESSAGE_TYPE_MASK;
rmsg->type = sz >> MESSAGE_TYPE_SHIFT;
skynet_harbor_send(rmsg, source, session);
} else {
struct skynet_message smsg;
smsg.source = source;
smsg.session = session;
smsg.data = data;
smsg.sz = sz;
if (skynet_context_push(destination, &smsg)) {//压入message queue
skynet_free(data);
return -1;
}
}
return session;
}
下面对压入message queue的skynet_context_push(destination, &smsg)剖析
int
skynet_context_push(uint32_t handle, struct skynet_message *message) {
struct skynet_context * ctx = skynet_handle_grab(handle);//由handle获取context
if (ctx == NULL) {
return -1;
}
skynet_mq_push(ctx->queue, message);//压入message queue 当in_global为全局时压入全局message queue work线程一直读message queue实现消息分发
skynet_context_release(ctx);
return 0;
}
当消息到达时会触发回调(init时设置)
static int
launch_cb(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_cb(l, context, msg, sz);//开始初始化
if (err) {
skynet_command(context, "EXIT", NULL);
}
return 0;
}
下面剖析init_cb(l, context, msg, sz);//开始初始化 这个函数主要是加载所有的lua脚本 至此lua层与c层服务交互完成
static int
init_cb(struct snlua *l, struct skynet_context *ctx, const char * args, size_t sz) {
lua_State *L = l->L;
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);
luaL_requiref(L, "skynet.profile", init_profile, 0);
int profile_lib = lua_gettop(L);
// replace coroutine.resume / coroutine.wrap
lua_getglobal(L, "coroutine");
lua_getfield(L, profile_lib, "resume");
lua_setfield(L, -2, "resume");
lua_getfield(L, profile_lib, "wrap");
lua_setfield(L, -2, "wrap");
lua_settop(L, profile_lib-1);
lua_pushlightuserdata(L, ctx);
lua_setfield(L, LUA_REGISTRYINDEX, "skynet_context");
luaL_requiref(L, "skynet.codecache", codecache , 0);
lua_pop(L,1);
lua_gc(L, LUA_GCGEN, 0, 0);
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);
const char * loader = optstring(ctx, "lualoader", "./lualib/loader.lua");
int r = luaL_loadfile(L,loader);//加载loader.lua 见如下剖析
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);
r = lua_pcall(L,1,0,1);
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);
if (lua_getfield(L, LUA_REGISTRYINDEX, "memlimit") == LUA_TNUMBER) {
size_t limit = lua_tointeger(L, -1);
l->mem_limit = limit;
skynet_error(ctx, "Set memory limit to %.2f M", (float)limit / (1024 * 1024));
lua_pushnil(L);
lua_setfield(L, LUA_REGISTRYINDEX, "memlimit");
}
lua_pop(L, 1);
lua_gc(L, LUA_GCRESTART, 0);
return 0;
}
详情见loader.lua
local args = {}
for word in string.gmatch(..., "%S+") do//匹配多个字符串
table.insert(args, word)
end
SERVICE_NAME = args[1]
local main, pattern
local err = {}
for pat in string.gmatch(LUA_SERVICE, "([^;]+);*") do//^只匹配开始部分 + 匹配一次或多次 ? 0次或一次 $ 匹配结尾 * 0次或多次(长匹配) - 0次或多次 故而为匹配以;结尾的字符串
local filename = string.gsub(pat, "?", SERVICE_NAME)//将传入的SERVICE_NAME传入? 如config的?.lua
local f, msg = loadfile(filename)
if not f then
table.insert(err, msg)
else
pattern = pat
main = f
break
end
end
if not main then
error(table.concat(err, "\n"))
end
LUA_SERVICE = nil//删除LUA_SERVICE
package.path , LUA_PATH = LUA_PATH//交换package.path LUA_PATH
package.cpath , LUA_CPATH = LUA_CPATH//同上
local service_path = string.match(pattern, "(.*/)[^/?]+$")//匹配多个../以/开始的0次或多次结束的多次这样的结尾
if service_path then
service_path = string.gsub(service_path, "?", args[1])//使用args[1]进行替换
package.path = service_path .. "?.lua;" .. package.path
SERVICE_PATH = service_path
else
local p = string.match(pattern, "(.*/).+$")
SERVICE_PATH = p
end
if LUA_PRELOAD then
local f = assert(loadfile(LUA_PRELOAD))
f(table.unpack(args))
LUA_PRELOAD = nil
end
main(select(2, table.unpack(args)))//main函数在前面解析时已经解析了 select是选择args开始的第二个参数开始调用
大部分服务都是通过luanch进行开启 launch会调用skynet_context_new函数进行开启 然后有个回调函数会init_cb 调用launcher.lua进行服务开启特定的lua脚本
在模式匹配可见外部链接【转载】