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


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


    参考文献

    skynet设计综述

    skynet源码赏析

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

    1、lua创建流程

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

    ​ 每个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 then
    		services[inst] = service .. " " .. param
    		instance[inst] = response
    	else
    		response(false)
    		return
    	end
    	return inst
    end
    
    function command.LAUNCH(_, service, ...)
    	launch_service(service, ...)
    	return NORET
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    这里实质是调用另外一个服务(.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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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 then
    		return tonumber("0x" .. string.sub(addr , 2))
    	end
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2、C语言流程

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

    通过上面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 launcher
    	if (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 launcher
    	struct 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.c
    		lua_newstate(lalloc, l);  //snlua_create  创建了一个lua_State
    		snlua是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 handle
    	ctx->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 last
    	context_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;
    	}
    }
    }  
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147

    此时,我们就已经创建了一个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 message
    	skynet_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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    初始化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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    3、调回到lua

    --[[
    	loader.lua
    	
    	在沙盒snlua中,加载并执行lua程序,可能执行如下几件事情:
        定义消息回调函数
        注册lua类型的消息回调函数
        注册除了lua类型以外的其他类型消息处理协议
        调用skynet.start函数,也就是将skynet.dispatch_message函数注册为lua服务的回调函数,所有派发给该lua服务的消息,都会传给这个函数;以及在下一帧执行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
    	local filename = string.gsub(pat, "?", SERVICE_NAME)
    	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
    package.path , LUA_PATH = LUA_PATH
    package.cpath , LUA_CPATH = LUA_CPATH
    
    local service_path = string.match(pattern, "(.*/)[^/?]+$")
    
    if service_path then
    	service_path = string.gsub(service_path, "?", 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
    
    _G.require = (require "skynet.require").require
    
    main(select(2, table.unpack(args)))
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

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

    • name:表示协议类型的字符串,如lua类型,其值则为”lua”
    • id:标识协议类型的整型值,类型有
    local skynet = {
    	-- read skynet.h
    	PTYPE_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 instead
    	PTYPE_DEBUG = 9,
    	PTYPE_LUA = 10,
    	PTYPE_SNAX = 11,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 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
    end
    
    skynet.dispatch("lua", function(_,_, command, ...)
    	local f = CMD[command]
    	skynet.ret(skynet.pack(f(...)))
    end)
    
    skynet.start(function()
    	-- TODO
    end)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

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

    -- skynet.lua
    
    ...
    
    ----- register protocol
    do
    	local REG = skynet.register_protocol
    
    	REG {
    		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
    
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

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

    -- skynet.lua
    function skynet.register_protocol(class)
    	local name = class.name
    	local id = class.id
    	assert(proto[name] == nil)
    	assert(type(name) == "string" and type(id) == "number" and id >=0 and id <=255)
    	proto[name] = class
    	proto[id] = class
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    现在回到我们的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 then
    		local ret = p.dispatch
    		p.dispatch = func
    		return ret
    	else
    		return p and p.dispatch
    	end
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这个函数,首先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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    这里将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()
    end
    
    function skynet.pcall(start)
    	return xpcall(init_template, debug.traceback, start)
    end
    
    function skynet.init_service(start)
    	local ok, err = skynet.pcall(start)
    	if not ok then
    		skynet.error("init service failed: " .. tostring(err))
    		skynet.send(".launcher","lua", "ERROR")
    		skynet.exit()
    	else
    		skynet.send(".launcher","lua", "LAUNCHOK")
    	end
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

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

  • 相关阅读:
    【C语言】模拟实现strcat
    JAVA8 Collectors.toMap value为null报错
    上传图片并显示#Vue3#后端接口数据
    六、Kafka-Eagle监控
    开开心心带你学习MySQL数据库之节尾篇
    《深入理解计算机系统》读书笔记1.1-1.5
    带你熟悉NLP预训练模型:BERT
    day05 ELasticsearch搜索引擎
    多商户商城系统功能拆解27讲-平台端分销结算设置
    解决php中通过exec调用python脚本报ModuleNotFoundError错误
  • 原文地址:https://blog.csdn.net/weixin_43730892/article/details/127906315