Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
coneo opened this issue May 3, 2015 · 0 comments
Open

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

coneo opened this issue May 3, 2015 · 0 comments
Labels

Comments

@coneo
Copy link
Owner

coneo commented May 3, 2015

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

@coneo coneo added the skynet label May 3, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant