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使用时间。
- skynet源码分析(11)--skynet的配置加载
- skynet源码分析(8)--skynet的网络
- skynet源码分析【skynet名字的管理】
- skynet源码分析(1)--模块加载
- skynet源码分析【skynet定时器服务的实现】
- skynet源码分析【skynet服务回调函数的实现】
- skynet源码分析(7)--skynet中的timer
- skynet定时器源码分析
- skynet 源码分析
- skynet源码分析【skynetsnaxd服务的实现】
- skynet源码分析(4)--monitor
- skynet框架 源码分析 一
- skynet框架 源码分析 二
- skynet框架 源码分析 三
- skynet框架 源码分析 四
- skynet框架 源码分析 五
- skynet消息队列源码分析
- skynet框架 源码分析 一
- 古文觀止卷八_后十九日復上宰相書_韓愈
- cocosd地图滚动算法
- Python ConfigParser
- poj1062 昂贵的婚礼 最短路变形 spfa 枚举 思考
- 随机森林-sklearn.ensemble.RandomForestRegressor
- skynet源码分析(11)--skynet的配置加载
- jobdu-1447-最短路
- Gym
- ArchLinux Openstreetmap 瓦片数字高程服务器演示网址变更解释
- 2017.08.22小结
- 解决U盘的写保护
- maven多配置
- Github 开源:使用 .NET WinForm 开发所见即所得的 IDE 开发环境(Sheng.Winform.IDE)【2.源代码简要说明】
- 双向A*算法浅析