skynet源码分析(11)--skynet的配置加载

来源:互联网 发布:宏村 知乎 编辑:程序博客网 时间:2024/05/16 13:54

作者:shihuaping0918@163.com,转载请注明作者

skynet中的源码已经分析得差不多了,还有启动过程没有分析。skynet的配置文件是以lua格式来写的。使用过skynet的都清楚skynet的启动命令是skynet config_file_name。配置文件名是作为命令行参数传给skynet进程的。

skynet进程启动以后,会读取config文件,然后解析这个lua文件。然后把相关的配置信息设置到lua的环境变量里。

C层读取配置的话是要从lua环境变量里去取的。所以C的配置数据结构的填充,对于某些人来说,是一团迷雾,不直观,而且难懂。

先看看配置文件路径的读取,skynet是用C写的,所以它的入口是main函数,在skynet_main.c中:

intmain(int argc, char *argv[]) {    const char * config_file = NULL ; //配置文件路径    if (argc > 1) {        config_file = argv[1]; //注意写死了,它就是第一个参数    } else {        fprintf(stderr, "Need a config file. Please read skynet wiki : https://github.com/cloudwu/skynet/wiki/Config\n"            "usage: skynet configfilename\n");        return 1;    }

这个配置文件的路径保存在config_file指针上,那么它是怎么加载的呢,这个又要涉及lua c api了。首先,云风写了一段lua代码,硬编码在skynet_main.c文件里,然后调用lua c api的函数去执行这段代码。这段lua代码会加载配置文件。先看一下这段lua代码:

static const char * load_config = "\    local result = {}\n\--函数 getenv--这个getenv是取进程的环境变量,比如$PATH    local function getenv(name) return assert(os.getenv(name), [[os.getenv() failed: ]] .. name) end\n\--取文件路径分隔符    local sep = package.config:sub(1,1)\n\--当前路径,linux下就是./    local current_path = [[.]]..sep\n\--函数 include    local function include(filename)\n\        local last_path = current_path\n\        local path, name = filename:match([[(.*]]..sep..[[)(.*)$]])\n\        if path then\n\            if path:sub(1,1) == sep then    -- root\n\                current_path = path\n\            else\n\                current_path = current_path .. path\n\            end\n\        else\n\            name = filename\n\        end\n\--加载文件        local f = assert(io.open(current_path .. name))\n\        local code = assert(f:read [[*a]])\n\        code = string.gsub(code, [[%$([%w_%d]+)]], getenv)\n\        f:close()\n\--注意load函数 @表示代码在文件里,t表示是文本        assert(load(code,[[@]]..filename,[[t]],result))()\n\        current_path = last_path\n\    end\n\--注意这里有元表操作    setmetatable(result, { __index = { include = include } })\n\--三个点代表可变参数    local config_name = ...\n\--这里调了include函数,参数是可变的    include(config_name)\n\--这里又有一个元表操作    setmetatable(result, nil)\n\    return result\n\";

对于package.config

775   lua_pushliteral(L, LUA_DIRSEP "\n" LUA_PATH_SEP "\n" LUA_PATH_MARK "\n"776                      LUA_EXEC_DIR "\n" LUA_IGMARK "\n");777   lua_setfield(L, -2, "config");

元表恐怕是需要单独两三篇文章才能介绍得比较全面了,因为这篇文章是分配配置加载的,对这里的元表操作只能介绍它是干嘛的,具体原理就不讲了。

lua中每一个值都有一个元表,这个元表就是lua表,定义了一个值在某些特定操作下的行为。比如gc,取表中元素等。或者更容易被接受的,数字类型的加法/减法等操作。

setmetatable就是用来替换元表的。不能再往深处讲了,再讲下去收不住了。

还有三个函数要说明一下,先说__index,__index实际上可以理解为t[key]:

__index: The indexing access table[key]. This event happens when table is not a table or when key is not present in table. The metamethod is looked up in table.

然后是assert,assert如果成功,返回它所有的参数

assert (v [, message])Calls [error](http://www.lua.org/manual/5.3/manual.html#pdf-error) if the value of its argument v is false (i.e., **nil** or **false**); otherwise, returns all its arguments. In case of error, message is the error object; when absent, it defaults to "assertion failed!"

然后是load函数,load函数返回一个lua函数。

不再继续介绍lua的编程知识了,还是直接说明load_config这段代码块的功能吧。它的功能实际上就是设一个Include函数,这个include在config文件中出现时,执行include函数,最终能够在config文件里加载include包含的lua文件。

    struct skynet_config config;    struct lua_State *L = luaL_newstate();    luaL_openlibs(L);   // link lua lib//加载load_config指向的lua代码块//参数2表示代码块//参数3表示代码块长度//参数4表示是文件中的代码块,不是文件,同时用于调试和报错//参数5表示代码块是文本格式,不是二进制    int err =  luaL_loadbufferx(L, load_config, strlen(load_config), "=[skynet config]", "t");    assert(err == LUA_OK);//参数入栈    lua_pushstring(L, config_file);//执行代码块,参数1个,返回结果1个    err = lua_pcall(L, 1, 1, 0);    if (err) {        fprintf(stderr,"%s\n",lua_tostring(L,-1));        lua_close(L);        return 1;    }    _init_env(L);

上面这段代码先加载了load_config对应的lua代码,然后执行它,实际上就是执行了include函数。include则会加载lua代码,最终这些代码形成了一个表格。这个表格呢会被_init_env用到。

static void_init_env(lua_State *L) {    lua_pushnil(L);  /* first key */    while (lua_next(L, -2) != 0) { //遍历表格       /* uses 'key' (at index -2) and 'value' (at index -1) */        int keyt = lua_type(L, -2);        if (keyt != LUA_TSTRING) {            fprintf(stderr, "Invalid config table\n");            exit(1);        }        const char * key = lua_tostring(L,-2);        if (lua_type(L,-1) == LUA_TBOOLEAN) {            int b = lua_toboolean(L,-1);//设置key/value到_ENV            skynet_setenv(key,b ? "true" : "false" );         } else {            const char * value = lua_tostring(L,-1);            if (value == NULL) {                fprintf(stderr, "Invalid config table key = %s\n", key);                exit(1);            }//设置key/value到_ENV            skynet_setenv(key,value);        }        lua_pop(L,1);    }    lua_pop(L,1);}

_init_env这个函数就是遍历load_config那段lua代码形成的表格,把里面的key和value全部取出来,然后设置到_ENV中。按前面的说法就是设置到lua的环境变量中。skynet_setenv再往下会讲到,那里面会说明最终做了什么操作。

文章的开头讲到C里面取配置是从lua环境变量里取的,它是怎么实现的呢?
C里面取配置有三个辅助函数,optint/optboolean/optstring

static intoptint(const char *key, int opt) {    const char * str = skynet_getenv(key); //这个就是从lua环境变量里取值    if (str == NULL) {        char tmp[20];        sprintf(tmp,"%d",opt);        skynet_setenv(key, tmp);        return opt;    }    return strtol(str, NULL, 10);}static intoptboolean(const char *key, int opt) {    const char * str = skynet_getenv(key);//这个就是从lua环境变量里取值    if (str == NULL) {        skynet_setenv(key, opt ? "true" : "false");        return opt;    }    return strcmp(str,"true")==0;}static const char *optstring(const char *key,const char * opt) {    const char * str = skynet_getenv(key);//这个就是从lua环境变量里取值    if (str == NULL) {        if (opt) {            skynet_setenv(key, opt);            opt = skynet_getenv(key);        }        return opt;    }    return str;}

optint/optboolean/optstring都是从lua环境变量里取值,取完了以后再做一次格式转换。同时这三个函数还提供一个默认值叫opt,如果环境变量里没有key对应的配置,就把这个默认值给设到环境变量里去。

下面看看从lua环境变量里取数据是怎么弄的。

const char * skynet_getenv(const char *key) {    SPIN_LOCK(E)    lua_State *L = E->L;    lua_getglobal(L, key); //从全局表中取名字key的值,然后把它压栈    const char * result = lua_tostring(L, -1);  //把这个值转换为string    lua_pop(L, 1); //把前面压栈的值弹出来,还原栈    SPIN_UNLOCK(E)    return result;}

skynet_getenv实际上就是从_ENV里取key对应的值,用大家比较熟悉的写法就是_ENV.key。

An assignment to a global name x = val is equivalent to the assignment _ENV.x = val (see [§2.2](http://www.lua.org/manual/5.3/manual.html#2.2)).

而skynet_setenv稍微曲折一点,先取_ENV.key,如果为nil,就操作_ENV.key=opt。

void skynet_setenv(const char *key, const char *value) {    SPIN_LOCK(E)    lua_State *L = E->L;    lua_getglobal(L, key); //取全局表中的key,压栈    assert(lua_isnil(L, -1)); //为空?这个key没设置过?    lua_pop(L,1); //还原栈    lua_pushstring(L,value); //入栈    lua_setglobal(L,key);  //把数据出栈,把key设进全局表    SPIN_UNLOCK(E)}

到了这里,准备知识基本上就讲完了。终于可以开始讲C里面的配置了。

_init_env(L);//名字叫thread的配置,默认为8    config.thread =  optint("thread",8);  //名字叫cpath的配置,默认为./cservice/?.so    config.module_path = optstring("cpath","./cservice/?.so");//名字叫harbor的配置,默认为1    config.harbor = optint("harbor", 1);//bootstrap脚本    config.bootstrap = optstring("bootstrap","snlua bootstrap");//daemon,默认是空    config.daemon = optstring("daemon", NULL);//logger日志文件名    config.logger = optstring("logger", NULL);//日志服务,默认是logger    config.logservice = optstring("logservice", "logger");//profile,优化选项,默认开启    config.profile = optboolean("profile", 1);

以上就是C层需要的配置列表,再详细介绍一下各个参数到底是什么意思。
thread,工作线程个数,这个在分析消息处理的时候介绍过了。
cpath,服务所在的路径,以;号分隔,可以是多个路径,这个在模块加载的时候介绍过了。
harbor,这个还没介绍过,是不是开启集群模式。
bootstrap,这个也没介绍过,就是一个脚本,主要做服务启动前准备工作。
daemon,是不是以后台模式运行skynet。
logger,日志通道,比如说文件,远程日志服务等方式。
logservice,日志服务,默认使用skynet自己提供的logger服务。
profile,优化开启,开启后会收集一些运行时的信息,通过日志和命令方式给码畜提供参考信息。分析cpu使用时间。

原创粉丝点击