skynetのソースコードを最初から読む(2)サーバー起動用のタスク前のブートストラップとskynte.newservice

      前の記事の分析の後、分析の2番目の部分は、元々、開始後にロジックを分析することを目的としていたため、人々はスカイネットフレームワークをより迅速に理解できます。しかし、それについて考えると、コードを開始するという考えを書き留めたほうがよいでしょう、私は理解しやすいと思います。

      ブートストラップはブートストラッププログラムの意味です。スカイネットでは、それは確かにサーバー作業を行うためのプレタスクです。

skynet_start.cで

//启动logger服务
struct skynet_context *ctx = skynet_context_new(config->logservice, config->logger);
    if (ctx == NULL) {
	fprintf(stderr, "Can't launch %s service\n", config->logservice);
	exit(1);
    }

skynet_handle_namehandle(skynet_context_handle(ctx), "logger");

//启动配置中的bootstrap服务
bootstrap(ctx, config->bootstrap);

//调用start传入配置线程数量
start(config->thread);

クエリ構成

渡されたパラメータが「snluabootsrtap」であることがわかり、関数の実現を確認できます。

static void
bootstrap(struct skynet_context * logger, const char * cmdline) {
    int sz = strlen(cmdline);
    char name[sz+1];
    char args[sz+1];
    sscanf(cmdline, "%s %s", name, args);
    //name = snlua, args = bootstrap
    struct skynet_context *ctx = skynet_context_new(name, args);
    if (ctx == NULL) {
	skynet_error(NULL, "Bootstrap error : %s\n", cmdline);
	skynet_context_dispatchall(logger);
	exit(1);
    }
}

次に、skynet_server.cのskynet_context_newメソッドを参照してください。

struct skynet_context * 
skynet_context_new(const char * name, const char *param) {
        //先查询snlua模块,这是一个C写的服务,在skynet_start.c中
        //skynet_statc方法,skynet_module_init(config->module_path)进行初始化
	struct skynet_module * mod = skynet_module_query(name);

	if (mod == NULL)
		return NULL;
        
        //分析1
        //这里创建一个新的lua虚拟机,所以会有各个服务的隔离
	void *inst = skynet_module_instance_create(mod);
	if (inst == NULL)
		return NULL;
	struct skynet_context * ctx = skynet_malloc(sizeof(*ctx));
	CHECKCALLING_INIT(ctx)

	ctx->mod = mod;
	ctx->instance = inst;
	ctx->ref = 2;
	ctx->cb = NULL;
	ctx->cb_ud = NULL;
	ctx->session_id = 0;
	ctx->logfile = 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)
        //分析2
        //这里使用snlua,启动传入的bootstrap.lua
	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の順序に従います。最初にskynet_module_instance_create、分析1を参照してください

void * 
skynet_module_instance_create(struct skynet_module *m) {
	if (m->create) {
		return m->create(); // 调用c模块的create
	} else {
		return (void *)(intptr_t)(~0);
	}
}

snluaの作成を参照してから、service_snlua.cを参照して新しいlua仮想マシンを構築し、サービス間に分離が行われるようにします。

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虚拟机
	return l;
}

仮想マシンを作成したら、分析2を参照して、skynet_module_instance_initに移動します。skynet_module.cで、実際には上記のコードコメントが示している内容であることがわかります。snluaのinitを使用してbootstarpを起動します。

int
skynet_module_instance_init(struct skynet_module *m, void * inst, struct skynet_context *ctx, const char * parm) {
	return m->init(inst, ctx, parm);
}

次に、service_snlua.luaに移動して、snluaを使用してluaスクリプトを開始する方法を確認します。skynetでは、Cモジュールの定義に、init、上記のm-> 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);
        //给需要初始化的服务设置回调函数为上面的luanch_cb
	skynet_callback(ctx, l , launch_cb);
	const char * self = skynet_command(ctx, "REG", NULL);
	uint32_t handle_id = strtoul(self+1, NULL, 16);
	// it must be first message
        // 给服务发送消息触发上面的launch_cb
        // 这里先知道触发上面,具体的消息处理我们的分析会说明
        // 给服务发送消息之后,具体的服务是怎么去处理的
	skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);
	return 0;
}

メッセージを送信した後、luanch_cbを呼び出します

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が呼び出されます。以下は、呼び出しプロセスを説明するための簡略化されたコードです。

static int
init_cb(struct snlua *l, struct skynet_context *ctx, const char * args, size_t sz) {
        ......
        
        //这里,就是使用loader.lua去加载我们的bootstrap
        const char * loader = optstring(ctx, "lualoader", "./lualib/loader.lua");

	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);
	r = lua_pcall(L,1,0,1); // 这里,就又通过C语言的lua接口,调用回了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);
	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;
}

ブートストラップが開始された場合は、bootstrap.luaを見下ろしてください。

skynet.start(function()
	local sharestring = tonumber(skynet.getenv "sharestring" or 4096)
	memory.ssexpand(sharestring)

	local standalone = skynet.getenv "standalone"

        // 这里又用snlua去启动了launcher.lua,启动过程和bootstrap一样
        // 这个launcher服务先记住,待会儿就知道干嘛的了
	local launcher = assert(skynet.launch("snlua","launcher"))
	skynet.name(".launcher", launcher)

        // 这下面还有一些是bootstrap这个前置任务做的
        // 这里skynet.newservice是啥?
	......
        skynet.newservice "service_mgr"
	pcall(skynet.newservice,skynet.getenv "start" or "main")
	skynet.exit()// 启动完上面的,完成了任务,这个服务就退出了

end)

skynet.newserviceを参照してください。これはskynetで、luaレイヤーが新しいサービスを開始するために使用します。skynet.luaでは、call.launcherが上記で開始したlauncher.luaを使用してサービスを開始していることがわかります。

function skynet.newservice(name, ...)
        // 这个参数 "LAUNCH", "snlua", name, ...特别注意一下
        // 最后也是用调用snlua去启动一个lua服务
	return skynet.call(".launcher", "lua" , "LAUNCH", "snlua", name, ...)
end

ランチャー、launcher.luaでLAUNCHを見ました

require "skynet.manager"	-- import manager apis

local function launch_service(service, ...)
	local param = table.concat({...}, " ")
	local inst = skynet.launch(service, param)
	local session = skynet.context()
	local response = skynet.response()
	if inst then
		services[inst] = service .. " " .. param
		instance[inst] = response
		launch_session[inst] = session
	else
		response(false)
		return
	end
	return inst
end

function command.LAUNCH(_, service, ...)
	launch_service(service, ...)
	return NORET
end

以下のmanager.luaのskyne.launchを参照してください

local c = require "skynet.core"

function skynet.launch(...)
	local addr = c.command("LAUNCH", table.concat({...}," "))
	if addr then
		return tonumber("0x" .. string.sub(addr , 2))
	end
end

c.commandを呼び出し、

ここで、skynet.coreはC言語モジュールです。この時点で、C言語の実装部分に入り、skynet.core.command( "LAUNCH"、 "snlua ...")を呼び出します。

最初にluaパートの内容を要約しましょう:

newservice–> skynet.call .launcher –>。launcher = skynet.launch( "snlua"、 "launcher")–> skynet.core.command( "LAUNCH"、 "snlua ...")

skynet.coreは実際にはlua_skynet.cで定義されており、そのコマンドはlcommand関数に対応しています。この時点でのパラメーターは実際にはlua_Stateにプッシュされます。

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);
	}

        // 这里就是调用skynet_server.c
	result = skynet_command(context, cmd, parm);
	if (result) {
		lua_pushstring(L, result);
		return 1;
	}
	return 0;
}

それは最終的にskynet_server.cでskynet_commandと呼ばれます

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 }, //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 },
};

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;
}

cmd_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");
    
        // 这里这几行是不是特别熟悉
        // 没错,就是和上面的bootstrap的启动一模一样
        // 就是使用snlua去启动另外的服务
	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;
	}
}

 

ご覧のとおり、その後luaレイヤーで使用したskynet.newserviceはすべて、launcher.luaを介して新しいサーバーを起動しました。

最後に、ブートストラップが行う重要なことの1つは、launcher.luaサービスを開始することです。次に、フレームワークのskynet.newserviceがランチャーを呼び出してluaサービスを開始します。

したがって、彼をサーバーのプレタスクと呼びます。新しいサービスを開始した後、コールバック関数をフックする方法などを後で分析します。

次の記事ブートストラップを共有した後、skynet_startは何をしましたか

おすすめ

転載: blog.csdn.net/banfushen007/article/details/111102233