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

作者: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中:

int
main(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 int
optint(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 int
optboolean(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使用时间。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容