[转]Skynet之斗转星移 - 将控制权交给Lua
Skynet之斗转星移 - 将控制权交给Lua
http://www.outsky.org/code/skynet-lua.html
Sep 7, 2014
在我看来,Skynet的一个重要优势是与Lua的高度结合,完全可以用Lua写服务。用C写服务的原理很简单:通过动态链接库的形式,提供create、init和release接口,供主进程在需要的时候载入服务,并将处理消息的回调函数一并注入主进程,这样,当主进程给此服务发消息时,消息就进入此回调函数处理。
由此可见,服务最重要的部分就是这个回调函数了,如果要用Lua来写服务的话,如何注册消息回调函数呢?且听我娓娓道来 :-)
从skynet.newservice看起
从Skynet的例子程序入口,它是这么启动一个Lua服务的:
skynet.newservice("simpledb")
跟踪发现,它最终调用了一个C导出函数:
-- destination: ".launcher"
-- type: PTYPE_LUA
-- session: 0
-- message: pack("LAUNCH", "snlua", "simpledb")
c.send(".launcher", skynet.PTYPE_LUA, nil,
skynet.pack("LAUNCH", "snlua", "simpledb"))
此函数的C源码在lualib-src/lua-skynet.c:_send,以上调用,意思就是:将数据包封装成消息发送给.launcher服务处理。
半路杀出个.launcher
接下来,就要找到.launcher服务,看看它怎么处理这个消息。
经过查找,发现.launcher服务是在service/bootstrap.lua中被注册的:
local launcher = skynet.launch("snlua", "launcher")
skynet.name(".launcher", launcher)
原来.launcher服务也是用Lua写的,大家不要慌,不要在乎这些细节,保持头脑清(蛋)醒(疼)。
虽然不明白这个skynet.launch是干什么的,但是感觉很厉害的样子。OK,跟进去看看。
不看不知道,一看吓一跳!
原来,它载入了Lua服务,并将注册后的服务的handle返回。
看来它是关键了,还等什么呢,快跟进去看看吧 :-)
它调用C接口skynet-src/skynet_server.c:cmd_launch:
cmd_launch(ctx, "snlua launcher")
此函数通过分析,调用skynet_context_new:
skynet_context_new("snlua", "launcher")
skynet_context_new就很简单了:
- 获取/加载C服务的动态库(snlua.so)
- 调用动态库的create函数(
snlua_create),获取服务的实例(snlua*) - 调用动态库的init函数(
snlua_init),并将参数("launcher")传入 - 为服务分配
skynet_context,并生成一个唯一的handle - 为服务分配消息队列,并将
skynet_context通过handle与之绑定 - 将消息队列压入全局消息队列中
由此可见,服务snlua是用C写的,且其create和init函数被相继调用,这两个函数做了服务的初始化工作。
struct snlua *
snlua_create(void) {
struct snlua * l = skynet_malloc(sizeof(*l));
memset(l,0,sizeof(*l));
l->L = lua_newstate(skynet_lalloc, NULL);
return l;
}
值得注意的是:snlua_create创建了全新的lua_State,这样就保证了每个Lua服务有自己独立的Lua虚拟机,互不影响。
// 在我们的跟踪中,args为"launcher"
int
snlua_init(struct snlua *l, struct skynet_context *ctx, const char * args) {
...
skynet_callback(ctx, l , _launch);
...
// it must be first message
// handle_id是它自己的handle
skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);
return 0;
}
和其他C服务一样,snlua_init设置了它的消息回调函数(_launch),这样以后发给它的消息都会交给_launch处理。
然后,它向自己发送了消息:"launcher",此消息将被_launch处理:
// 在我们的跟踪过程中args为"launcher"
static int
_launch(struct skynet_context * context, void *ud, int type, int session, uint32_t source , const void * msg, size_t sz) {
struct snlua *l = ud;
skynet_callback(context, NULL, NULL);
_init(l, context, msg, sz);
...
}
这里将snlua服务的callback值空,就导致下次发给此服务的消息不会进入_launch,那以后的消息怎么办?直接丢掉吗?跟下去就知道了。
static int
_init(struct snlua *l, struct skynet_context *ctx, const char * args, size_t sz) {
lua_State *L = l->L;
...
lua_pushlightuserdata(L, ctx);
lua_setfield(L, LUA_REGISTRYINDEX, "skynet_context");
...
const char * loader = optstring(ctx, "lualoader", "./lualib/loader.lua");
int r = luaL_loadfile(L,loader);
lua_pushlstring(L, args, sz); // 在我们的跟踪过程中args为"launcher"
r = lua_pcall(L,1,0,1);
...
}
_init对新生成的lua_State做必要的设置,比如查找路径等。并将skynet_context压入注册表(registry),供以后使用(其实是为Lua提供接口的skynet.so需要它)。
然后,它载入loader.lua并将参数("launcher")传递过去。
loader.lua的功能很简单:找到参数("launcher")指定的Lua文件,运行它。
好了,终于出来了,现在找到launcher.lua,看看它是干啥的。
它主要调用了这三个函数:skynet.register_protocol、skynet.dispatch和skynet.start。
skynet.register_protocol将指定的协议记录到一张表里面,后续通过协议的名字(如:"lua")或id(如:skynet.PTYPE_LUA)都可以找到此协议。
skynet.dispatch设置指定协议的dispatch方法。
skynet.start比较有意思:
function skynet.start(start_func)
c.callback(dispatch_message)
skynet.timeout(0, function()
init_service(start_func)
end)
end
之前snlua将callback函数值空,现在在这里被重新赋值了,以后发过来的消息都会被dispatch_message处理。
然后就调用传入skynet.start的函数。
(skynet.lua比较复杂,看来有必要好好研究研究再写一篇研究报告出来,现在先略过吧 - -)
至此,.launcher服务我们已经跟到底了:
- 通过snlua(C服务)为自己生成一套服务所需的数据(skynet_context、handle、message queue、callback等),并将自己注册进主进程,接受消息调度
- 偷梁换柱的将snlua的callback换成自己的Lua版本
- 通过
skynet.register_protocol和skynet.dispatch注册某种消息的处理函数
不忘初心
虽然上面的跟踪,已经可以看出Skynet斗转星移的过程了,可是我们开头是从例子skynet.newservice("simpledb")开始的,所以,还得回去看看。
c.send(".launcher", skynet.PTYPE_LUA, nil,
skynet.pack("LAUNCH", "snlua", "simpledb"))
这句会被塞入.launcher的消息队列,进而被dispatch到它的skynet.PTYPE_LUA协议处理函数中处理:
-- cmd: "LAUNCH"
-- ...: "snlua", "simpledb"
skynet.dispatch("lua", function(session, address, cmd, ...)
...
local f = command[cmd]
...
local ret = f(address, ...)
...
end)
-- service: "snlua"
-- ...: "simpledb"
function command.LAUNCH(_, service, ...)
launch_service(service, ...)
...
end
-- service: "snlua"
-- ...: "simpledb"
local function launch_service(service, ...)
...
local inst = skynet.launch(service, param)
...
end
看到了吧,最终还是回到了上面跟过的skynet.launch,只不过上次传的是"launcher"这次传的是"simpledb"。
结果也一样,通过snlua,将自己注册到主进程,然后偷偷的把消息回调函数换成自己的。
总结一下
- 通过
skynet.newservice载入一个Lua服务 - snlua帮助Lua服务做一些底层的工作
- 生成一个独立的
lua_State - 生成一个
skynet_context - 分配一个
handle - 生成一个私有的消息队列
skynet_context和消息队列通过handle关联
- 生成一个独立的
- 将消息回调从snlua转移到Lua层(
skynet.lua:dispatch_message)
相关阅读
[转]Skynet之斗转星移 - 将控制权交给Lua的更多相关文章
- skynet 源码阅读笔记 bootstrap.lua
最近几周粗略看了 skynet 代码的 C 部分.遇到很多知识点以前只是知道,但并不十分了解,所以这是一个学习的过程. 从 main 函数开始,闷头一阵看下来,着实蛋疼. 当看了 skynet_mq. ...
- 【转】Skynet之消息队列 - 消息的存储与分发
Skynet之消息队列 - 消息的存储与分发 http://www.outsky.org/code/skynet-message-queue.html Sep 8, 2014 按我的理解,消息队列是S ...
- Lua语言中文手册 转载自网络
Programming in LuaCopyright ® 2005, Translation Team, www.luachina.net Programming in LuaProgramming ...
- Lua 学习之基础篇九<Lua 协同程序(Coroutine)>
引言 讲到协程,首先来介绍一下线程和协程的区别 lua协程和多线程 相同之处:拥有自己独立的桟.局部变量和PC计数器,同时又与其他协程共享全局变量和其他大部分东西 不同之处:一个多线程程序可以同时运行 ...
- cocos2d-x + Lua接入iOS原生SDK的实现方案[转]
相信很多朋友在使用cocos2d-x+lua开发游戏时都遇到过接入iOS原生SDK的问题,比如常见的接应用内支付SDK,广告SDK或是一些社交平台SDK等等,我也没少接过这类SDK.这篇文章主要是对我 ...
- 在redis一致性hash(shard)中使用lua脚本的坑
redis 2.8之前的版本,为了实现支持巨量数据缓存或者持久化,一般需要通过redis sharding模式来实现redis集群,普遍大家使用的是twitter开源的Twemproxy. twemp ...
- [原创]cocos2d-x + Lua接入iOS原生SDK的实现方案
相信很多朋友在使用cocos2d-x+lua开发游戏时都遇到过接入iOS原生SDK的问题,比如常见的接应用内支付SDK,广告SDK或是一些社交平台SDK等等,我也没少接过这类SDK.这篇文章主要是对我 ...
- 转让lua性能executeGlobalFunction
没有其他的,搞搞cocos2dx的lua文字,话lua这件事情在几年前学过一段时间.还曾对自己c++介面,我已经做了一些小东西.只是时间的流逝,模糊记忆. 拿起点功夫和成本.下面是我的一些经验. co ...
- openresty源码剖析——lua代码的加载
##Openresty是什么 OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台,通过把lua嵌入到Nginx中,使得我们可以用轻巧的lua语言进行nginx的相关开发,处理 ...
随机推荐
- C# 3.0 / C# 3.5 扩展方法
概述 扩展方法是一种特殊的静态方法,可以像扩展类型上的实例方法一样进行调用,能向现有类型“添加”方法,而无须创建新的派生类型.重新编译或以其他方式修改原始类型. 扩展方法的定义实现: public s ...
- 十三、MVC的WEB框架(Structs2)
一.Structs2的应用 Structs2是基于MVC的WEB框架.一般基于框架的程序要运行成功,对于JAR包的版本,配置文件的正确性有着苛刻的要求,一个地方错了,都会导致框架程序运行出错. 1.首 ...
- java 大数处理
头文件:import java.util.*; import java.math.*; Scanner cin = Scanner (System.in);//读入 while(cin.hasNext ...
- MSSQL内存架构及管理
1. MSSQL内存架构 相比较Oracle,MSSQL的内存区域并没那么清晰,但和Oracle类似,MSSQL内存区域大体也可以分为三个部分:buffer pool,query/workspace ...
- Django信息安全相关之CSRF和XSS
什么是xss攻击 xss攻击:黑客把恶意的JS脚本提交到后端,后端把恶意脚本渲染显示出来 什么是CSRF? 1.你登录建行官网后 你的浏览器就保存了建行网站的cokie,(如果不关闭这个页面c ...
- java 常用命令
#查看堆使用情况jmap -heap [pid]#查看占用内存高的对象jmap -histo:live [pid] | head -n 100#查看占用内存高的对象,dump成文件,线下分析jmap ...
- 整合Spring Security(二十七)
在这一节,我们将对/hello页面进行权限控制,必须是授权用户才能访问.当没有权限的用户访问后,跳转到登录页面. 添加依赖 在pom.xml中添加如下配置,引入对Spring Security的依赖. ...
- 随机数类Random
我们来学习下,用来产生随机数的类Random,它也属于引用数据类型. 这个Random类,它可以产生多种数据类型的随机数,在这里我们主要介绍生成整数与小数的方式. l 方法简介 public int ...
- Android 编码规范
1.文件编码:UTF-8 源文件编码格式为 UTF-8. 2.数组初始化:可写成块状结构 数组初始化可以写成块状结构,比如,下面的写法都是OK的: new int[] { 0, 1, 2, 3 } n ...
- php composer 使用 以及 psr0和psr4的真正区别
composer 使用 项目和库之间唯一的区别是,你的项目是一个没有名字的包 包名不区分大小写,但惯例是使用小写字母,并用连字符作为单词的分隔 入门比较好资源: 官网适合读一遍 https://do ...