作者:shihuaping0918@163.com观话,轉(zhuǎn)載請注明作者
兩個月前接觸skynet,最初使用的時候過程是相當痛苦的,而且網(wǎng)絡上可以找到的學習資料并不多权烧。當時決定寫一些skynet相關的文章,最近終于有空了檐束,開始寫這個東西辫秧。
skynet是云風開源的一個游戲框架,底層是c被丧,中間層和上層都是lua盟戏。基于actor模型甥桂,使用消息隊列進行內(nèi)部通信柿究。萬丈高樓平地起,先開始看最底層的內(nèi)容吧黄选,因為上層的會涉及一些業(yè)務蝇摸,而最底層的只涉及一些系統(tǒng)調(diào)用,理解起來更簡單办陷。
閱讀代碼使用的工具是eclipse cdt貌夕。代碼提交tag是f94ca6f
skynet底層代碼位于skynet/skynet-src下,模塊加載相關在skynet-module.c skynet-module.h這兩個文件里。這里的模塊在linux下指的是so民镜,在windows下指的是dll啡专,在skynet中指的是config中配置的cpath下的文件。
//以下四行為函數(shù)指針聲明
typedef void * (*skynet_dl_create)(void);
typedef int (*skynet_dl_init)(void * inst, struct skynet_context *, const char * parm);
typedef void (*skynet_dl_release)(void * inst);
typedef void (*skynet_dl_signal)(void * inst, int signal);
//單個模塊的結(jié)構(gòu)體
struct skynet_module {
const char * name; //模塊名
void * module; //模塊指針
skynet_dl_create create; //create函數(shù)
skynet_dl_init init; //init函數(shù)
skynet_dl_release release; //release函數(shù)
skynet_dl_signal signal; //signal函數(shù)
};
//添加一個模塊
void skynet_module_insert(struct skynet_module *mod);
//查詢一個模塊
struct skynet_module * skynet_module_query(const char * name);
//某個模塊中的create函數(shù)調(diào)用
void * skynet_module_instance_create(struct skynet_module *);
//某個模塊中的init函數(shù)調(diào)用
int skynet_module_instance_init(struct skynet_module *, void * inst, struct skynet_context *ctx, const char * parm);
//某個模塊中的release函數(shù)調(diào)用
void skynet_module_instance_release(struct skynet_module *, void *inst);
//某個模塊中的signal函數(shù)調(diào)用
void skynet_module_instance_signal(struct skynet_module *, void *inst, int signal);
//初始化模塊管理
void skynet_module_init(const char *path);
從上面的代碼可以看出制圈,每個模塊需要實現(xiàn)四個最基本的函數(shù)们童,create/init/release/signal。注意這里并不是說函數(shù)名字叫這個鲸鹦,函數(shù)名字具體叫什么下面會講到慧库。
#define MAX_MODULE_TYPE 32
//這里定義了模塊列表數(shù)據(jù)結(jié)構(gòu)
struct modules {
int count;
struct spinlock lock;
const char * path;
struct skynet_module m[MAX_MODULE_TYPE]; //最多只能加載32個模塊
};
static struct modules * M = NULL;
//內(nèi)部函數(shù),打開一個動態(tài)庫
static void *
_try_open(struct modules *m, const char * name) {
const char *l;
const char * path = m->path;
size_t path_size = strlen(path);
size_t name_size = strlen(name);
int sz = path_size + name_size;
//search path
void * dl = NULL;
char tmp[sz];
//遍歷路徑查找so亥鬓,路徑以;分隔
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);
}
//dlope打開so
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;
}
//根據(jù)模塊名在模塊列表中查找
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 void *
get_api(struct skynet_module *mod, const char *api_name) {
size_t name_size = strlen(mod->name);
size_t api_size = strlen(api_name);
char tmp[name_size + api_size + 1];
//將模塊名附到tmp中
memcpy(tmp, mod->name, name_size);
//將方法名附到tmp中
memcpy(tmp+name_size, api_name, api_size+1);
char *ptr = strrchr(tmp, '.');
if (ptr == NULL) {
ptr = tmp;
} else {
ptr = ptr + 1;
}
// dlsym是一個系統(tǒng)函數(shù)完沪,根據(jù)函數(shù)名字獲取函數(shù)地址(指針)
return dlsym(mod->module, ptr);
}
static int
open_sym(struct skynet_module *mod) {
mod->create = get_api(mod, "_create"); //獲取create方法
mod->init = get_api(mod, "_init"); //獲取init方法
mod->release = get_api(mod, "_release"); //獲取release方法
mod->signal = get_api(mod, "_signal"); //獲取signal方法
return mod->init == NULL; //然而這里只判定只要實現(xiàn)了init就可以了
}
//根據(jù)模塊名查找模塊
struct skynet_module *
skynet_module_query(const char * name) {
//先到列表里查
struct skynet_module * result = _query(name);
if (result)
return result;
SPIN_LOCK(M)
result = _query(name); // double check
//在列表里沒查到
if (result == NULL && M->count < MAX_MODULE_TYPE) {
int index = M->count;
//打開so
void * dl = _try_open(M,name);
if (dl) {
M->m[index].name = name;
M->m[index].module = dl;
//獲取so中的init/create/release/signal方法地址
if (open_sym(&M->m[index]) == 0) {
M->m[index].name = skynet_strdup(name);
M->count ++;
result = &M->m[index];
}
}
}
SPIN_UNLOCK(M)
return result;
}
//添加模塊到模塊列表
void
skynet_module_insert(struct skynet_module *mod) {
SPIN_LOCK(M)
//模塊是不是已經(jīng)在列表中了
struct skynet_module * m = _query(mod->name);
assert(m == NULL && M->count < MAX_MODULE_TYPE);
int index = M->count;
M->m[index] = *mod;
++M->count;
SPIN_UNLOCK(M)
}
void *
skynet_module_instance_create(struct skynet_module *m) {
if (m->create) {
return m->create(); //對應上文說的,調(diào)用模塊的create函數(shù)
} else {
return (void *)(intptr_t)(~0);
}
}
int
skynet_module_instance_init(struct skynet_module *m, void * inst, struct skynet_context *ctx, const char * parm) {
return m->init(inst, ctx, parm); //對應上文說的,調(diào)用模塊的init函數(shù)
}
void
skynet_module_instance_release(struct skynet_module *m, void *inst) {
if (m->release) {
m->release(inst); //對應上文說的覆积,調(diào)用模塊的release函數(shù)
}
}
void
skynet_module_instance_signal(struct skynet_module *m, void *inst, int signal) {
if (m->signal) {
m->signal(inst, signal); //對應上文說的听皿,調(diào)用模塊的release函數(shù)
}
}
//初始化模塊列表數(shù)據(jù)結(jié)構(gòu)
void
skynet_module_init(const char *path) {
struct modules *m = skynet_malloc(sizeof(*m));
m->count = 0;
m->path = skynet_strdup(path);
SPIN_INIT(m)
M = m;
}
skynet_module_init在skynet-main.c中被調(diào)用,傳進來的path是在運行時config中配置的宽档,如果config文件中沒有配置cpath尉姨,默認將cpath的值設為./cservice/?.so,加載cpath目錄下的so文件吗冤。
從get_api可以看出來又厉,skynet要求模塊的create/init/release/signal方法的命名是模塊名加一個下劃線,后面帶create/init/release/signal椎瘟。在skynet/service-src目錄下有現(xiàn)成的例子覆致,大家可以去看一下。
到這里肺蔚,整個模塊加載功能就分析完了煌妈。從啟動流程來分析是,首先在config文件中配置一個cpath宣羊,它包含了你想要加載的so的路徑璧诵。然后skynet-main.c在啟動的時候會把cpath讀出來,設進moduls->path中仇冯。在skynet-server.c中的skynet_context_new中會調(diào)用skynet_module_query之宿,skynet_module_query首先會在列表中查詢so是否已經(jīng)加載,如果沒有就直接加載它苛坚。
模塊一定要包含有四個函數(shù)init/create/release/signal比被,它的命名格式為,假定模塊名為xxx炕婶,那么就是xxx_create/xxx_init/xxx_release/xxx_signal姐赡。這四個函數(shù)是干嘛用的?
create做內(nèi)存分配柠掂。init做初始化项滑,它可能會做一些其它的事情,比如打開網(wǎng)絡涯贞,打開文件枪狂,函數(shù)回調(diào)掛載等等。relase做資源回收宋渔,包括內(nèi)存資源州疾,文件資源,網(wǎng)絡資源等等皇拣,signal是發(fā)信號严蓖,比如kill信號薄嫡,告訴模塊該停了。