@wsd1
2017-01-19T07:59:40.000000Z
字数 2698
阅读 2313
skynet
http://cloudwu.github.io/lua53doc/manual.html#6.4.1
https://github.com/cloudwu/skynet/wiki
skynet一个关键的优势是使用lua语言撰写脚本,而使用脚本语言写逻辑的一个大好处就是可以使用顺序逻辑描述业务。表面的平整之下实际是C语言对lua虚拟机的调度器在起作用。
阻塞API从lua中yield回C代码中,之后有了事件再次resume,看起来实现很简单,但是更加复杂的是错误的处理,API调用不知道会经历多少艰辛,出错、超时如何处理?这个是关键所在。
上回书说到:
上篇大致跟踪了snlua模块被导入的过程,作为一个c模块,其也是被编译成so文件被加载的。其init中安装callback之后,发送一个消息给自己,在消息处理函数中,去掉了自己模块的callback,并且引导lua代码。
观众可以看一下 skynet的wiki之 LuaAPI,复习一下:
https://github.com/cloudwu/skynet/wiki/LuaAPI
服务的启动和退出
每个 skynet 服务都必须有一个启动函数。这一点和普通 Lua 脚本不同,传统的 Lua 脚本是没有专门的主函数,脚本本身即是主函数。而 skynet 服务,你必须主动调用 skynet.start(function() ... end) 。 skynet.start 注册一个函数为这个服务的启动函数。当然你还是可以在脚本中随意写一个 Lua 代码,它们会先于 start 函数执行。但是,不要在外面调用 skynet 的阻塞 API ,因为框架将无法唤醒它们。
lua脚本也不是随意写的,无论如何总得把 callback 安装上吧,这样才能继续响应外部事件不是。
首先,引用skynet库:
local skynet = require "skynet"
在 lualib/skynet.lua中:
function skynet.start(start_func)
c.callback(skynet.dispatch_message)
//第一件事就是安装callback
skynet.timeout(0, function()
skynet.init_service(start_func)
end)
/*
timeout()函数设置一个0时间的定时器,能直接使用刚才我们安装的callback;
另外,更为重要的是其start_func可能会yield,所以不能直接使用pcall。
*/
end
代码很简单,注释都说明白了,下面关键看看timer机制如何工作,道理上这里一定用了skynet的timer机制。
function skynet.timeout(ti, func)
local session = c.intcommand("TIMEOUT",ti)
--上面会调用c代码,安装一个定时器,返回其session。定时器发作时会发回相同session的msg
local co = co_create(func)
session_id_coroutine[session] = co
--上面很好理解,就是新建co,将session作为索引记录之
end
先看timeout实现:
在skynet_server.c
static const char *
cmd_timeout(struct skynet_context * context, const char * param) {
char * session_ptr = NULL;
int ti = strtol(param, &session_ptr, 10);
int session = skynet_context_newsession(context);
skynet_timeout(context->handle, ti, session);
sprintf(context->result, "%d", session);
return context->result;
}
很清晰,接收到传递进来的 时间和session,使用skynet_timeout()函数安排了定时器,可以想见,到时间之后,会有带着session信息的消息被发出。下一节可见详情。
co_create()包装了coroutine的相关操作,设计精妙,值得研究:
local function co_create(f)
--删除并返回最后一个
local co = table.remove(coroutine_pool)
if co == nil then
--新建
co = coroutine.create(function(...)
f(...) --运行之~
while true do
f = nil
coroutine_pool[#coroutine_pool+1] = co
--跑完了,为啥还加入pool?原因是这个栈我们可以给其他func用
--关键位置,所有的co_routine都结束于此,yield "EXIT",等着返回新的func
f = coroutine_yield "EXIT"
--关键位置,上面从resume又拿到新的func,同一个coroutine换上司机继续嗨
--详解:看到f()的参数“coroutine_yield()”了吗?为何不用f()直接嗨起来?而是先yield?
--原因是,这里我们不想深入运行f(),毕竟本函数目的是返回一个corotine嘛。进入后打个转又出来了。
-- *小星星*
f(coroutine_yield())
end
end)
else
-- 这里很重要,下面的代码意味着co_create会陷入运行吗?NO NO NO
-- 云大的巧妙设计,你想象不到
-- 这里仅仅将f()“安装”进入coroutine,然后就退出来了。且后面对co resume的时候 还能够传递参数进去,真棒。
--这里对应看上面 *小星星* 指示的位置
coroutine_resume(co, f)
end
return co
end
至此,timeout已经设置好了,skynet.start()函数顺利退出,这一段lua脚本也可以正常退出。
注意,所有lua代码中执行的内容,包括建立coroutine管理的表,都是放置在lua_state结构里的,而我们又要回归到 service_snlua.c中。
准确的说,是回到 _init()函数中:
r = lua_pcall(L,1,0,1);
这一句的后面。又回到skynet的主循环中。
下一节,我们看看skynet在c层面如何调用callback来实施外部事件