Description
把一个符合规范的 C 模块,从动态库(so 文件)中启动起来,绑定一个永不重复(即使模块退出)的数字 id 做为其 handle 。模块被称为服务(Service),服务间可以自由发送消息。每个模块可以向 Skynet 框架注册一个 callback 函数,用来接收发给它的消息。每个服务都是被一个个消息包驱动,当没有包到来的时候,它们就会处于挂起状态,对 CPU 资源零消耗。如果需要自主逻辑,则可以利用 Skynet 系统提供的 timeout 消息,定期触发。
skynet的设计是按模块划分的,这里的一个独立的模块在skynet中就是一个服务。一个独立的服务就是一个独立的功能模块。比如你可以把日志模块作为一个服务,也可以把数据库读写作为一个服务。服务与服务之间通过消息来通讯。一般来说,服务是由消息驱动的,当消息到达就触发一个回调。这里的通讯是由skynet底层做好的,你如果写一个新的服务并不难。
这部分代码是skynet的核心,采用C编写,下面我们就来看看服务在skynet中到底是个什么东西,他是如何工作的:
/*一个服务的结构,表示为一个skynet上下文*/
struct skynet_context {
void * instance; //具体服务实例地址,如logger服务实例
struct skynet_module * mod; //模块结构
void * cb_ud; //具体服务的实例地址
skynet_cb cb; //唯一的回调函数,即该服务的消息处理回调
struct message_queue *queue; //该服务拥有的消息队列
FILE * logfile; //日志的文件句柄
char result[32]; //储存一些查询返回,类似errCode
uint32_t handle; //唯一的标识
int session_id; //会话id,生成session用
int ref; //引用计数,在计数为零时会清除该服务
bool init; //是否已初始化
bool endless; //是否已结束
CHECKCALLING_DECL //检查调用声明,一个宏
};
这就是一个skynet中服务的结构定义,其中的核心结构是struct skynet_module * mod,其中skynet_module结构定义如下:
struct skynet_module {
const char * name; //模块名称
void * module; //模块实例,是一个动态链接库实例
skynet_dl_create create; //创建函数
skynet_dl_init init; //初始化函数
skynet_dl_release release; //释放函数
skynet_dl_signal signal; //信号处理函数
};
在启动skynet的时候,会加载一些列模块(module),其中模块是一个动态链接库(so文件),每个模块必须满足一定的条件:可以设定初始回调(_init),创建回调(_create),释放回调(_release)和信号处理回调(_signal)。定义这几个函数的时候必须是 " 模块名+下划线+{init, create, release, signal} ",而且对这几个接口的参数也有约定。
例如skynet自带的logger模块定义如下:
struct logger *logger_create(void);
void logger_release(struct logger * inst);
int logger_init(struct logger * inst, struct skynet_context *ctx, const char * parm);
我们先定义下服务和模块?
服务(skynet_contex)是指skynet中的一个独立的支持,表示的是一个相对全局的概念,它是一个模块加载之后在内存中的一个表示。服务与服务之间有底层的通讯支持,实现服务间的交流。
模块(skynet_module)这里指从动态链接库(so文件)加载到内存的一个表示。
我们首先来看看加载一个服务模块都做了什么?
一个服务是通过如下接口创建的:
struct skynet_context * skynet_context_new(const char * name, const char *param);
它会根据模块名称(name)去创建一个模块(skynet_module),skynet会把已加载的模块存在一个32大小的全局数组中(也就是说一个skynet最多只能创建32个服务)。在创建一个新模块时会根据模块名称首先去这个全局数组里找,如果没有才去创建。创建一个模块的过程就如上面说的,实际上就是加载一个动态链接库(so文件)。
在创建服务时,会为每个服务创建一个消息队列(message_queue),并挂载到全局队列中,供工作线程处理(这里的队列处理在以后会专门讲解)。
skynet有一个全局的句柄管理器handle_storage来管理服务,根据服务的handle(uint32)作一个hash计算后把服务实例存到一个列表中。我们可以通过服务的handle或者name来获取服务实例。
释放服务通过接口skynet_context_release完成,通过前面的结构体分析服务是按引用计数的。那么该接口实际上会先把服务的引用计数减一,如果计数为零,那么会调用真正的清除接口清除服务。真正的清除服务操作会关闭该服务的日志文件句柄,释放模块实例(动态链接库实例),释放消息队列,全局的服务节点减一(一种全局管理)。