skynet
是一个为网络游戏服务器设计的轻量框架,采用单进程,多线程架构。底层是c
,中间层和上层都是lua
。基于actor
模型,使用消息队列进行内部通信。
总体思路
skynet
是一个很好的actor实例。通过给每个worker
分配lua vm
,有效地实现了actor
的思路。
简单说,可以把skynet
理解为一个简单的操作系统,它可以用来调度数千个lua
虚拟机,让它们并行工作。每个lua
虚拟机都可以接收处理其它虚拟机发送过来的消息,以及对其它虚拟机发送消息。每个lua
虚拟机,可以看成skynet
这个操作系统下的独立进程,你可以在skynet
工作时启动新的进程、销毁不再使用的进程、还可以通过调试控制台监管它们。skynet
同时掌控了外部的网络数据输入,和定时器的管理;它会把这些转换为一致的(类似进程间的消息)消息输入给这些进程。
上图其实就是 Actor 模型,Actor 模型是一个概念模型,用于处理并发计算。它定义了一系列系统组件应该如何动作和交互的通用规则,即 Actor 的参与者 = {消息队列, 处理逻辑(服务)}。
Actor实现
那在 skynet 底层中,如何描述一个 Actor 参与者呢? 通过上面的结构体关系图可知,其实在 skynet 底层使用了结构体 struct skynet_context 来描述一个 Actor 参与者,这个结构体的定义如下:
1 | struct skynet_context { |
instance,cb,queue,cb_ud。这四个字段,构成了一个 Actor 核心骨架,消息队列内的消息被服务的回调函数 cb 执行,这也是为什么说 skynet 是消息驱动的。一个大致的流程是:一个消息通过服务的回调函数,然后传递给服务实例,最终被消费处理掉。
1 | // 回调函数类型 |
这里需要注意下,在服务实例第一次初始化并设置回调后,cb_ud 与 instance 是同一个东西,即ctx->instance = ctx->cb_ud,但是对于 snlua 服务启动后,会重新设置一次回调,这里就产生了两个区别的:
1.cb_ud 指向由原来的 ctx->instance 变成了 lua vm(lua 主线程),它们存在这样的一个关系:ctx->instance->L = ctx->cb_ud = luavm;
2.cb 回调函数变成了一个封装了 lua 调用的函数;
具体的更改流程的代码如下
1 | static int |