Skip to content

skynet源码剖析(2) - 服务 #16

Open
@coneo

Description

@coneo

skynet核心只解决一个问题

把一个符合规范的 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完成,通过前面的结构体分析服务是按引用计数的。那么该接口实际上会先把服务的引用计数减一,如果计数为零,那么会调用真正的清除接口清除服务。真正的清除服务操作会关闭该服务的日志文件句柄,释放模块实例(动态链接库实例),释放消息队列,全局的服务节点减一(一种全局管理)。

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions