skynet_module提供一个模板来实现各种不同类型的c服务,比如,snlua(最常见),logger,gate等。
先说明两个linux系统函数:
// dlopen函数用来获取so库的访问句柄。本质是将so库加载到内存中,并返回一个可以访问该内存块的句柄 void *dlopen(const char *filename, int flags); //The function dlopen() loads the dynamic shared object (shared library) file named by the null-terminated string filename and returns an opaque "handle" for the loaded object. // dlsym函数,以dlopen返回的handle为第一个参数,获取so库中名称为symbol的函数指针,通过该指针可以获得so库里的函数地址 void *dlsym(void *handle, const char *symbol); //The function dlsym() takes a "handle" of a dynamic loaded shared object returned by dlopen(3) along with a null-terminated symbol name, and returns the address where that symbol is loaded into memory
skynet_module的结构如下:
struct modules { int count; struct spinlock lock; const char * path;// 用c编写的服务编译后so库路经,不配置默认是./cservice/?.so struct skynet_module m[MAX_MODULE_TYPE]; //存放服务模块的数组 }; static struct modules * M = NULL; struct skynet_module { //单个模块结构 const char * name; //c服务名称,一般是指c服务的文件名 void * module; //访问so库的dl句柄,通过dlopen获得该句柄 skynet_dl_create create; //通过dlsym绑定so库中的xxx_create函数,调用create即调用xxx_create接口 skynet_dl_init init; //绑定xxx_init接口 skynet_dl_release release; //绑定xxx_release接口 skynet_dl_signal signal; //绑定xxx_signal接口 };
M->path在初始化(skynet_module_init)时赋值,对应配置文件的cpath,不配置默认是./cservice/?.so。
用c编写的服务编译成so库后放在cpath目录下,当创建一个ctx时(skynet_context_new),通过名称在skynet_module里找对应的module(skynet_module_query),如果M->m存有同名称的module,返回即可。
如果第一次创建该名称的服务,先找到该名称对应的so库的路径,然后通过dlopen函数获取so库的访问句柄dl(try_open),再通过dlsym函数获取so库中xxx_create, xxx_init, xxx_release, xxx_signal4个函数地址(open_sym),将这些地址赋值给skynet_module->create, skynet_module->init, skynet_module->release, skynet_module->signal。通常一个c服务需要提供这4个接口,定义这些接口时一定要以服务名称为前缀,通过下划线和函数名称连接起来:
xxx_create:创建ctx过程中调用,通常是申请内存。返回该服务的实例inst,设置ctx->instance=inst,之后init,release,signal都需要用到该实例
xxx_init:创建ctx期间调用,除了初始化,最主要的工作是向ctx注册callback函数(skynet_callback),之后ctx才能正确的处理收到的消息(调用callback函数)
xxx_release:释放ctx时调用(skynet_context_release)
xxx_signal:ctx收到信号时调用
最后将skynet_module保存到M->m里,之后创建同名称ctx就不用获取so库的访问句柄了。