skynet启动lua服务
每个skynet进程在启动的时候,都会启动一个lua层的launcher服务器,该服务主要负责skynet的运作期间进行创建其他lua服务.
launcher是在 bootstrap.lua中启动的, skynet.launch(“snlua”,“launcher”)) 这里启动,关于
skynet.launch方法,后面会与描述.
在skynet.lua中 skynet.newservice(),代码如下
function skynet.newservice(name, ...) --启动一个LUA服务,实际上是给launcher发送消息实现的
return skynet.call(".launcher", "lua" , "LAUNCH", "snlua", name, ...)
end
调用skynet.lua中 skynet.call()
function skynet.call(addr,typename,...) --阻塞发送消息
local p = proto[typename] --现根据名字取得对应的消息类别(table)
local session = c.send(addr,p.id,nil,p.pack(...)) --p.pack打包消息,然后发送消息,保存返回的会话
if session == nil then
error("call to invalid address" .. skynet.address(addr))
end
return p.unpack(yield_call(addr,session)) --挂起调用,解包返回的消息,消息大小
end
c.send 从lua-skynet.c的 luaopen_skynet_core中对应的函数名跟函数发生调用,send对应
lsend函数,lsend调用send_message
static int send_message(lua_State *L, int source,int idx_type){
struct skynet_context* context = lua_touserdata(L,lua_upvalueindex(1)); //从伪索引获取上下文
uint32_t dest = lua_tounsigned(L,1); //获取目标数字地址
const char* dest_string = NULL; //目标字符串地址
if(dest == 0) { //传入的是数字,而不是字符串
dest_string = get_dest_string(L,1); //获取目标字符串地址
}
int type = luaL_checkinteger(L,2); //获取消息类型
int session = 0;
if (lua_isnil(L,3) { //如果第三个参数是空,则是从skynet.call调用过来的
type |= PTYPE_TAG_ALLOCESSION; //skynet.call会在内部生成一个唯一session,所以这里需要设置分配会话标志
}else{
session = luaL_checkinteger(L,3); //获取会话
//skynet.send调用过来的传递的是0
}
int mtype == lua_type(L,4); //取得第四个参数的类型
switch(mtype) { //判断类型
case LUA_TSTRING:{ //如果是字符串类型
size_t len = 0; //存放字符串长度
void* msg = (void*)lua_tolstring(L,4,&len); //获取字符串
if (len == 0 ) msg = NULL; 置空
if (dest_string){ //目标是字符串地址
session = sknet_sendname(context,0,dest_string,type,session,msg,len);
}esle {
session = skynet_send(context,0,dest,type,session,msg,len);
}
break;
}
case LUA_TLIGHTUSERDATA: { //用户数据
void * msg = lua_touserdata(L,4); //获取消息c指针
int size = luaL_checkinteger(L,5); //获取消息长度
if (dest_string) {//如果目标是字符串地址
session = skynet_sendname(context, 0, dest_string, type | PTYPE_TAG_DONTCOPY, session, msg, size);//不需要拷贝消息数据
} else {//如果目标是数字地址
session = skynet_send(context, 0, dest, type | PTYPE_TAG_DONTCOPY, session, msg, size);//不需要拷贝消息数据
}
break;
}
default:
luaL_error(L,"seynet.send invalid param %s", lua_typename(L, lua_type(L,4)));
}
if (session < 0 ) {
// send to invalid address
// todo: maybe throw error whould be better
// 发送到无效的地址
// 可能抛出一个错误更好
return 0;
}
lua_pushinteger(L,session);//返回会话
return 1;
}
这里调用skynet_server.c中的skynet_send()方法
int skynet_send(struct_context* context,uint32_t source, uint32_t destination, int type, int session, void* data, size_t sz) {
if ((sz & HANDLE_MASK) != sz) { //判断消息是否过大
skynet_error(context,"The message to %x is too large (sz=%lu)",destination,sz);
skynet_free(data);
return -1;
}
_filter_args(context,type,&session,(void*)&data,&sz); //过滤参数
if(source == 0 ) { //源为0
source = context->handle; //设置源为自己
}
if (destination == 0){ //目的地址0
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;
skynet_harbor_send(rmsg,source,session); //用harbor讲消息发出去
}else { //本地消息
struct skynet_message smsg; //定义一个skynet消息
smsg.source = source;
smsg.session = session;
smsg.data = data;
smsg.sz = sz;
//发送消息到目标的队列
if (skyet_context_push(destination,&smsg)) {
skynet_free(data); //push失败,释放数据
return -1;
}
}
return session; //返回会话
}
这里的skynet_server.c中的skynet_context_push
int skynet_context_push(uint32_t handle, struct skynet_message* message){
struct skynet_context* ctx= skynet_handle_grab(handle); //根据句柄获取上下文引用
if (ctx == NULL) {
return -1;
}
skynet_mq_push(ctx->queue,message); //将消息放入队列
skynet_context_release(ctx); //释放上下文,因为在skynet_handle_grab中调用了skynet_context_grab增加了上下文的引用计数
return 0;
}
这里调用到skynet_mq.c中的skynet_mq_push()
void skynet_mq_push(struct message_queue* q,struct skynet_message* message){
assert(message);
SPIN_LOCK(q);
q->queue[q->tail] == *message;
if (++q->tail > q->cap){
q->tail = 0;
}
if (q->head == q->tail) {
expand_queue(q);
}
if (q->in_global == 0) {
q->in_global = MQ_IN_GLOBAL;
skynet_golbalmq_push(q);
}
SPIN_UNLOCK(q);
}
这就是启动一个服务的逻辑就是向launcher的服务队列中发送了需要启动的服务name,接下来就看launchar服务是如何启动一个服务的.
launcher服务收到消息后,就去处理,调用launcher.lua中的launch_service()方法
local function launch_service(service,...)
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
reponse(false) -- 启动失败,抛出异常
return
end
return inst
end
这里调用manager.lua中的sknet.launch方法
function skynet.launch(...)
local addr = c.command("LAUNCH", table.concat({...}," "))
if addr then
return tonumber("0x" .. string.sub(addr,2))
end
end
这里则用调用到lua-skynet.c中的luaopen_skynet_core对应的方法 command字段对应lcommand方法
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* parm = NULL; //参数
if (lua_gettop(L) == 2) { //传入的参数有2个
parm = luaL_checkstring(L,2); //取出参数串
}
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 },
{ "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;
}
根据传入的LAUNCHER参数,得知对应的方法为skynet_server.c 中的 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"); //参数
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()方法来创建一个服务