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)

skynet教程(3)--服务的别名

三个问题带你看懂多核并发框架skynet源码

游戏开发实战手把手教你从零跑一个Skynet,详细教程,含案例讲解(服务端 | Skynet | Ubuntu)

游戏开发实战手把手教你从零跑一个Skynet,详细教程,含案例讲解(服务端 | Skynet | Ubuntu)