本文主要参考&转载:skynet源码赏析
本文旨在记录我对skynet重新学习和理解的过程,也便于以后回顾(本文纯手打,输出的过程也是记忆的过程)。
创建一个c服务,一般要经历下面几个步骤:
- 从modules列表中,查找对应的服务模块,如果找到则返回,否则到modules的path中去查找对应的so库,创建一个skynet_module对象(数据结构见上节),将so库加载到内存,并将访问该so库的句柄和skynet_module对象关联(_try_open做了这件事),并将so库中的xxx_create,xxx_init,xxx_signal,xxx_release四个函数地址赋值给skynet_module的create、init、signal和release四个函数中,这样这个skynet_module对象,就能调用so库中,对应的四个接口(_open_sym做了这件事)。
- 创建一个服务实例即skynet_context对象,他包含一个次级消息队列指针,服务模块指针(skynet_module对象,便于他访问module自定义的create、init、signal和release函数),由服务模块调用create接口创建的数据实例等。
- 将新创建的服务实例(skynet_context对象)注册到全局的服务列表中(见上节的handle_storage结构)。
- 初始化服务模块(skynet_module创建的数据实例),并在初始化函数中,注册新创建的skynet_context实例的callback函数
- 将该服务实例(skynet_context实例)的次级消息队列,插入到全局消息队列中。
经过上面的步骤,一个c服务模块就被创建出来了,在回调函数被指定以后,其他服务发送给他的消息,会被pop出来,最终传给服务对应的callback函数,最后达到驱动服务的目的。
以创建logger服务为例子:
-
启动skynet节点时,会启动logger服务
// skynet_start.c skynet_start(struct skynet_config * config) { ... struct skynet_context *ctx = skynet_context_new(config->logservice, config->logger); if (ctx == NULL) { fprintf(stderr, "Can't launch %s service\n", config->logservice); exit(1); } ... }
config->logservice指log服务的so库名称,用于后面加载服务时使用,config->logger指log的输出路径。
- 在modules.skynet_module列表中查找logger服务模块,如果没找到则在so库中寻找,通过dlopen函数获得logger服务部模块的句柄,通过dlsym函数将其内的logger_create、logger_init、logger_release三个函数的地址赋值给logger模块中的create、init、release函数指针。
- 通过logger模块的create函数创建服务实例
在skynet_context_new函数会malloc一个skynet_context,将logger服务实例赋值给skynet_context的instance变量。// service_logger.c struct logger { FILE * handle; char * filename; uint32_t starttime; int close; }; struct logger * logger_create(void) { struct logger * inst = skynet_malloc(sizeof(*inst)); inst->handle = NULL; inst->close = 0; inst->filename = NULL; return inst; }
- 为了使skynet_context能访问logger服务内部函数,将logger模块指针赋值给skynet_context中的mod变量。此时一个服务对象所要用到的逻辑能够通过mod变量访问logger服务对应的函数,而通过instance可以找到服务自身的数据块。
- 将新建的skynet_context加入到skynet_context列表中。
- 创建服务对应message_queue消息队列
- 初始化logger服务,注册logger服务的callback函数
// service_logger.c static int logger_cb(struct skynet_context * context, void *ud, int type, int session, uint32_t source, const void * msg, size_t sz) { struct logger * inst = ud; switch (type) { case PTYPE_SYSTEM: if (inst->filename) { inst->handle = freopen(inst->filename, "a", inst->handle); } break; case PTYPE_TEXT: if (inst->filename) { char tmp[SIZETIMEFMT]; int csec = timestring(ud, tmp); fprintf(inst->handle, "%s.%02d ", tmp, csec); } fprintf(inst->handle, "[:%08x] ", source); fwrite(msg, sz , 1, inst->handle); fprintf(inst->handle, "\n"); fflush(inst->handle); break; } return 0; } int logger_init(struct logger * inst, struct skynet_context *ctx, const char * parm) { ... if (inst->handle) { skynet_callback(ctx, inst, logger_cb); return 0; } return 1; } // skynet_server.c void skynet_callback(struct skynet_context * context, void *ud, skynet_cb cb) { context->cb = cb; context->cb_ud = ud; }
- 将logger服务的次级消息队列加入到global_queue全局消息队列中