skynet中动态库的处理
Posted mr_yu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了skynet中动态库的处理相关的知识,希望对你有一定的参考价值。
skynet中的.so动态库由service-src中的c文件编译完后生成,其中最重要的是snlua.c.
源码地址:https://github.com/cloudwu/skynet/service-src
这里不介绍如何生成动态库,而是介绍当编译成动态库后,skynet是如何利用里边的函数的.
源码:
#include "skynet.h"
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
struct snlua {
lua_State * L;
struct skynet_context * ctx;
};
// LUA_CACHELIB may defined in patched lua for shared proto
#ifdef LUA_CACHELIB
#define codecache luaopen_cache
#else
static int
cleardummy(lua_State *L) {
return 0;
}
static int
codecache(lua_State *L) { //该函数由_init中luaL_requiref(L, /modname/ "skynet.codecache", /openf/ codecache , 0)
luaL_Reg l[] = {
{ "clear", cleardummy },
{ "mode", cleardummy },
{ NULL, NULL },
};
luaL_newlib(L,l); //向lua中注册“clear”和“mode”函数
lua_getglobal(L, "loadfile");//把全局变量 name 里的值压栈,返回该值的类型,目前尚不知道loadfile是在哪里注册到lua中的?
lua_setfield(L, -2, "loadfile");//做一个等价于 t[k] = v 的操作, 这里 t 是给出的索引处的值, 而 v 是栈顶的那个值。
return 1;
}
#endif
static int
traceback (lua_State *L) {
const char *msg = lua_tostring(L, 1);
if (msg)
luaL_traceback(L, L, msg, 1);
else {
lua_pushliteral(L, "(no error message)");
}
return 1;
}
static void
_report_launcher_error(struct skynet_context *ctx) {
// sizeof "ERROR" == 5
skynet_sendname(ctx, 0, ".launcher", PTYPE_TEXT, 0, "ERROR", 5);
}
static const char *
optstring(struct skynet_context *ctx, const char *key, const char * str) {
const char * ret = skynet_command(ctx, "GETENV", key);
if (ret == NULL) {
return str;
}
return ret;
}
static int
_init(struct snlua *l, struct skynet_context *ctx, const char * args, size_t sz) {
lua_State *L = l->L;
l->ctx = ctx;
lua_gc(L, LUA_GCSTOP, 0); //LUA_GCSTOP: 停止垃圾收集器
lua_pushboolean(L, 1); /* signal for libraries to ignore env. vars. */ //放个nil值到栈上
lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV"); //LUA_REGISTRYINDEX?,"LUA_NOENV"?
luaL_openlibs(L);
lua_pushlightuserdata(L, ctx); //lua中的lightuserdata存放的是c语言中的变量,这里为结构体指针
lua_setfield(L, LUA_REGISTRYINDEX, "skynet_context");
luaL_requiref(L, /modname/ "skynet.codecache", /openf/ codecache , 0);
//如果 modname 不在 package.loaded 中, 则调用函数 openf ,并传入字符串 modname。 将其返回值置入 package.loaded[modname]。 这个行 为好似该函数通过 require 调用过一样。
//如果glb 为真, 同时也讲模块设到全局变量 modname 里。
//在栈上留下该模块的副本。
lua_pop(L,1);
const char *path = optstring(ctx, "lua_path","./lualib/?.lua;./lualib/?/init.lua");
lua_pushstring(L, path);
lua_setglobal(L, "LUA_PATH");
const char *cpath = optstring(ctx, "lua_cpath","./luaclib/?.so");
lua_pushstring(L, cpath);
lua_setglobal(L, "LUA_CPATH");
const char *service = optstring(ctx, "luaservice", "./service/?.lua");
lua_pushstring(L, service);
lua_setglobal(L, "LUA_SERVICE");
const char *preload = skynet_command(ctx, "GETENV", "preload");
lua_pushstring(L, preload);
lua_setglobal(L, "LUA_PRELOAD"); //
lua_pushcfunction(L, traceback); //以上将各种path注册到lua的全局变量中,变量名字即为 "LUA_xxxx"
assert(lua_gettop(L) == 1);
const char * loader = optstring(ctx, "lualoader", "./lualib/loader.lua");
int r = luaL_loadfile(L,loader); //加载loader.lua
if (r != LUA_OK) {
skynet_error(ctx, "Can\'t load %s : %s", loader, lua_tostring(L, -1));
_report_launcher_error(ctx);
return 1;
}
lua_pushlstring(L, args, sz); //将bootstart传入
r = lua_pcall(L,1,0,1); //load bootstart 服务
if (r != LUA_OK) {
skynet_error(ctx, "lua loader error : %s", lua_tostring(L, -1));
_report_launcher_error(ctx);
return 1;
}
lua_settop(L,0);
lua_gc(L, LUA_GCRESTART, 0);
return 0;
}
static int
_launch(struct skynet_context * context, void *ud, int type, int session, uint32_t source , const void * msg, size_t sz) { //ud就是服务中的结构体
assert(type == 0 && session == 0); //确保_launch服务在一个进程中只调用一次
struct snlua *l = ud;
skynet_callback(context, NULL, NULL);
int err = _init(l, context, msg, sz);
if (err) {
skynet_command(context, "EXIT", NULL);
}
return 0;
}
int
snlua_init(struct snlua *l, struct skynet_context *ctx, const char * args) { //BOOTSTART
int sz = strlen(args);
char * tmp = skynet_malloc(sz);
memcpy(tmp, args, sz);
skynet_callback(ctx, l , _launch); //注册回掉函数为_launch,当工作线程轮询到的时候调用的回掉函数即为"_launch"
const char * self = skynet_command(ctx, "REG", NULL); //服务有handle和名字,这里为服务注册名字,若名字为NULL,则返回handle的字符串 “:handle”
uint32_t handle_id = strtoul(self+1, NULL, 16);
// it must be first message
skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz); //给他自己发送一个信息,目的地为刚注册的地址,最终将该消息push到对应的mq上
return 0;
}
struct snlua *
snlua_create(void) {
struct snlua * l = skynet_malloc(sizeof(*l));
memset(l,0,sizeof(*l));
l->L = lua_newstate(skynet_lalloc, NULL);
return l;
}
void
snlua_release(struct snlua *l) {
lua_close(l->L);
skynet_free(l);
}
void
snlua_signal(struct snlua *l, int signal) {
skynet_error(l->ctx, "recv a signal %d", signal);
#ifdef lua_checksig
// If our lua support signal (modified lua version by skynet), trigger it.
skynet_sig_L = l->L;
#endif
}
以上为service_snlua.c中的内容,初看起来一头雾水,我们来看一下整个过程。
skynet.main.c中的main函数最后调用skynet_start.c中的skynet_start函数,其中启动snlua服务代码部分如下:
bootstrap(ctx, config->bootstrap); //创建snlua服务模块,及ctx
这句话是启动snlua模块,具体bootstrap代码:
static void
bootstrap(struct skynet_context * logger, const char * cmdline) {
int sz = strlen(cmdline);
char name[sz+1];
char args[sz+1];
sscanf(cmdline, "%s %s", name, args); //最终name = "snlua" , args = "bootstrap"
struct skynet_context *ctx = skynet_context_new(name, args); //启动snlua , bootstarp为参数
if (ctx == NULL) {
skynet_error(NULL, "Bootstrap error : %s\\n", cmdline);
skynet_context_dispatchall(logger);
exit(1);
}
}
函数参数 : logger,忽略
cmdline : " snlua bootstrap”
让我们看一下skynet_contex_new( "snlua" , "bootstrap" )都做了些什么,该函数在skynet_server.c中
struct skynet_module {
const char * name;
void * module; 动态链接库的指针.so
skynet_dl_create create;
skynet_dl_init init;
skynet_dl_release release;
skynet_dl_signal signal; //skynet.module.c
}; //结构体中的create init release signal 分别对应着service_snlua.c中的create init release signal的函数地址,skynet_dl_XX是宏定义
struct skynet_context *
skynet_context_new(const char * name, const char *param) { //name为服务名,param为需要的参数
struct skynet_module * mod = skynet_module_query(name); //根据name即"snlua"来得到对应名字的mod地址,有就直接查到,没有先打开动态库,获取里边的函数地址,并注册该服务,并返回
//module里实际上是改动态库中的内容
if (mod == NULL) //正常情况下mod不会为NULL
return NULL;
void *inst = skynet_module_instance_create(mod); //调用mod中的create函数,实际上就是service_snlua.c中的create,即调用dlopen来获得动态库
if (inst == NULL)
return NULL;
struct skynet_context * ctx = skynet_malloc(sizeof(*ctx)); //为每个服务创建一个ctx
CHECKCALLING_INIT(ctx)
ctx->mod = mod; //里边有动态库指针和动态库中的函数指针及该库的名字,根据名字已经注册到了skynet
ctx->instance = inst; //每个服务都有一个结构体,如logger的指针
ctx->ref = 2; //why
ctx->cb = NULL;
ctx->cb_ud = NULL;
ctx->session_id = 0;
ctx->logfile = NULL;
ctx->init = false;
ctx->endless = false;
// Should set to 0 first to avoid skynet_handle_retireall get an uninitialized handle
ctx->handle = 0;
ctx->handle = skynet_handle_register(ctx); //将该ctx注册到handle中
struct message_queue * queue = ctx->queue = skynet_mq_create(ctx->handle);
// init function maybe use ctx->handle, so it must init at last
context_inc();
CHECKCALLING_BEGIN(ctx)
int r = skynet_module_instance_init(mod, inst, ctx, param); //执行mod中的init函数,若为0执行成功
CHECKCALLING_END(ctx)
if (r == 0) {
struct skynet_context * ret = skynet_context_release(ctx); //减少ctx的引用计数,若为0,删除ctx资源
if (ret) {
ctx->init = true;
}
skynet_globalmq_push(queue); //将创建的服务对应的队列push到全局队列
if (ret) {
skynet_error(ret, "LAUNCH %s %s", name, param ? param : "");
}
return ret;
} else {
skynet_error(ctx, "FAILED launch %s", name);
uint32_t handle = ctx->handle;
skynet_context_release(ctx);
skynet_handle_retire(handle);
struct drop_t d = { handle };
skynet_mq_release(queue, drop_message, &d);
return NULL;
}
}
接下来我们看一下skynet_module.c中的关于动态库看起
应该从skynet_module_query(const char * name)看起 , 此处name为“snlua”
struct skynet_module *
skynet_module_query(const char * name) {
struct skynet_module * result = _query(name);//首先查找snlua module是否存在
if (result)
return result; //存在则返回
SPIN_LOCK(M)
result = _query(name); // double check
if (result == NULL && M->count < MAX_MODULE_TYPE) { //不存在,接下来就是重点
int index = M->count; //M中记录共有多少个c module创建了,->count 为下一个module的索引
void * dl = _try_open(M,name); //调用dlopen来打开动态库,返回动态库指针
if (dl) {
M->m[index].name = name;
M->m[index].module = dl;
if (_open_sym(&M->m[index]) == 0) { //调用_open_sym来获取动态库中的函数地址,具体看下面实现
M->m[index].name = skynet_strdup(name);
M->count ++;
result = &M->m[index];
}
}
}
SPIN_UNLOCK(M)
return result;
}
struct modules {
int count;
struct spinlock lock;
const char * path;
struct skynet_module m[MAX_MODULE_TYPE];
};
static struct modules * M = NULL;
static void *
_try_open(struct modules *m, const char * name) { M , “snlua”
const char *l;
const char * path = m->path; //path就是初始化M时传入的动态库所在的路径
size_t path_size = strlen(path);
size_t name_size = strlen(name); //“snlua”
int sz = path_size + name_size;
//search path
void * dl = NULL;
char tmp[sz];
do
{
memset(tmp,0,sz);
while (*path == \';\') path++;
if (*path == \'\\0\') break;
l = strchr(path, \';\');
if (l == NULL) l = path + strlen(path);
int len = l - path;
int i;
for (i=0;path[i]!=\'?\' && i < len ;i++) {
tmp[i] = path[i];
}
memcpy(tmp+i,name,name_size);
if (path[i] == \'?\') {
strncpy(tmp+i+name_size,path+i+1,len - i - 1);
} else {
fprintf(stderr,"Invalid C service path\\n");
exit(1);
} //以上循环为拼凑出完成的动态库路径,用真是动态库名字替代 “?”
dl = dlopen(tmp, RTLD_NOW | RTLD_GLOBAL); //打开动态库,返回动态库指针
path = l;
}while(dl == NULL);
if (dl == NULL) {
fprintf(stderr, "try open %s failed : %s\\n",name,dlerror());
}
return dl;
}
static struct skynet_module *
_query(const char * name) {
int i;
for (i=0;i<M->count;i++) {
if (strcmp(M->m[i].name,name)==0) {
return &M->m[i];
}
}
return NULL;
}
static int
_open_sym(struct skynet_module *mod) {
size_t name_size = strlen(mod->name);
char tmp[name_size + 9]; // create/init/release/signal , longest name is release (7)
memcpy(tmp, mod->name, name_size);
strcpy(tmp+name_size, "_create");
mod->create = dlsym(mod->module, tmp);//tmp为snlua_create
strcpy(tmp+name_size, "_init");
mod->init = dlsym(mod->module, tmp);//snlua_init
strcpy(tmp+name_size, "_release");//snlua_release
mod->release = dlsym(mod->module, tmp);
strcpy(tmp+name_size, "_signal");
mod->signal = dlsym(mod->module, tmp); //snlua_signal
return mod->init == NULL;
}
总结:snlua相当于lua服务的入口,在snlua的 _init函数中根据参数名称如(bootstrap)等启动对应的lua服务。
即首先到module中查找该c服务(例如snlua)是否存在,若不存在则调用动态库函数打开该库,获取该库的必要函数。
然后为该服务创建队列,并放到全局消息队列中。
skyney中每个动态库,都是扩展的服务,有自己的消息队列,回掉函数
下面介绍下标题内容即动态库操作的函数,这里用到:
#include <dlfcn.h> void *dlopen(const char *filename, int flag); char *dlerror(void); void *dlsym(void *handle, const char *symbol); int dlclose(void *handle);
具体使用参考:http://www.cnblogs.com/Anker/p/3746802.html
未完,待续。
以上是关于skynet中动态库的处理的主要内容,如果未能解决你的问题,请参考以下文章
游戏开发实战教你Unity通过sproto协议与Skynet框架的服务端通信,附工程源码(Unity | Sproto | 协议 | Skynet)
游戏开发实战教你Unity通过sproto协议与Skynet框架的服务端通信,附工程源码(Unity | Sproto | 协议 | Skynet)