skynet启动读取配置文件浅析(skynet_main.c)
1.作为skynet的启动文件,主要完成了一些初始化和读取并存取配置文件内容的工作. 在这里只将代码读取配置文件的部分抽取出来,就算没有skynet环境,这些代码也是可以运行的,了解以后再对照源码进行分析,希望能对理解skynet带来一些帮助
#include "lua.h" #include "lualib.h" #include "lauxlib.h" #include <signal.h> #include <assert.h> #include "env.h" struct config { int thread; int harbor; const char *deamon; const char *module_path; const char *bootstrap; const char *logger; const char *logservice; }; void popn(lua_State *L, int n); static int optint(const char *key, int opt) { const char *str = env_getenv(key); if (str == NULL) { char tmp[20]; sprintf(tmp, "%d", opt); env_setenv(key, tmp); return opt; } return strtol(str, NULL, 10); } static const char * optstring(const char *key, const char *opt) { const char *str = env_getenv(key); if (str == NULL) { if (opt) { env_setenv(key, opt); opt = env_getenv(key); } return opt; } return str; } static const char * load_config = "\ local config_name = ...\ local f = assert(io.open(config_name))\ local code = assert(f:read \'*a\')\ print(\"code is \", code)\ local function getenv(name) return assert(os.getenv(name), \'os.getenv() failed: \' .. name) end\ code = string.gsub(code, \'%$([%w_%d]+)\', getenv)\ f:close()\ print(\"code after replace is \", code)\ local result = {}\ assert(load(code,\'=(load)\',\'t\',result))()\ return result\ "; static void _init_env(lua_State *L) { lua_pushnil(L); while(lua_next(L, -2) != 0) { 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); env_setenv(key, b ? "true" : "false"); } else { const char *value = lua_tostring(L, -1); if (value == NULL) { fprintf(stderr, "Invalud config table key = %s\n", key); exit(1); } env_setenv(key, value); } lua_pop(L, 1); } lua_pop(L, 1); } void popn(lua_State *L, int n) { lua_pop(L, -(n) - 1); } int sigign() { struct sigaction sa; sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, 0); return 0; } void init_conf(struct config *conf) { conf->thread = optint("thread", 8); conf->module_path = optstring("cpath", "./cservice/?.so"); conf->harbor = optint("harbor", 1); conf->bootstrap = optstring("bootstrap", "snlua bootstrap"); conf->deamon = optstring("deamon", NULL); conf->deamon = optstring("logger", NULL); conf->logservice = optstring("logservice", "logger"); } void test_env() { printf("thread: %s\n", env_getenv("thread")); printf("harbor: %s\n", env_getenv("harbor")); } 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"); } sigign(); env_env_init(); struct config conf; struct lua_State *L = luaL_newstate(); luaL_openlibs(L); int err = luaL_loadstring(L, load_config); assert(err == LUA_OK); lua_pushstring(L, config_file); 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); init_conf(&conf); test_env(); lua_close(L); return 0; }
首先看main函数:
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"); }
这句话是读取配置文件路径:正如运行skynet的命令行为:./skynet ./examples/config.我将config拿出来放到和可执行文件用级,运行方式为: ./test ./config
于是config_file = argv[1]便保存了配置文件名.
下面两句: sigign(); env_env_init();
设置信号处理函数,调用env_env_init()初始化保存配置文件的环境,该函数在env.c中,对应的是skynet_env.c(这里内容是一样的),代码如下:
env.c:
#include "env.h" #include "spinlock.h" #include "lauxlib.h" #include "lua.h" #include <stdlib.h> #include <assert.h> #include <error.h> #include <string.h> struct env { struct spinlock lock; lua_State *L; }; static struct env *E = NULL; const char* env_getenv(const char *key) { SPIN_LOCK(E); lua_State *L = E->L; lua_getglobal(L, key); const char *result = lua_tostring(L, -1); //nil may return if there is no such a key-pair. lua_pop(L, 1); SPIN_UNLOCK(E); return result; } void env_setenv(const char *key, const char *value) { SPIN_LOCK(E); lua_State *L = E->L; lua_getglobal(L, key); //after getglobal , if there is a value crosponds to the key , then it will be pushed onto stack, otherwise nil is pushed assert(lua_isnil(L, -1)); lua_pop(L, 1); //pop nil from the stack lua_pushstring(L, value); lua_setglobal(L, key); //after setglobal the value on the stack will be poped. SPIN_UNLOCK(E); } void env_env_init() { E = (struct env*)malloc(sizeof(*E)); SPIN_INIT(E); E->L = luaL_newstate(); }
由此可见env.c创建了静态变量 static struct env *E 来保存配置。
继续main函数:
struct config conf; struct lua_State *L = luaL_newstate(); luaL_openlibs(L); int err = luaL_loadstring(L, load_config); assert(err == LUA_OK); lua_pushstring(L, config_file); 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);
这几句话便完成了读取配置文件并保存到env.c中的E的lua虚拟机里.
int err = luaL_loadstring(L, load_config);
这句话将load_config作为一个chunk加载到打开的L的函数堆栈里,我们看一下load_config:
static const char * load_config = "\ local config_name = ...\ local f = assert(io.open(config_name))\ local code = assert(f:read \'*a\')\ print(\"code is \", code)\ local function getenv(name) return assert(os.getenv(name), \'os.getenv() failed: \' .. name) end\ code = string.gsub(code, \'%$([%w_%d]+)\', getenv)\ f:close()\ print(\"code after replace is \", code)\ local result = {}\ assert(load(code,\'=(load)\',\'t\',result))()\ return result\ ";
发现load_config其实是一段lua代码字符串,load到函数栈上以后,便可以执行,
但是根据 local config_name = ...\ 看出,需要传入一个参数,也就是配置文件的路径 那么如何传呢?继续看main函数 lua_pushstring(L, config_file); 这句话便将config_file这个字符串放到了栈上供
int err = luaL_loadstring(L, load_config); 产生的chunk调用.
于是执行 err = lua_pcall(L, 1, 1, 0); if (err) { fprintf(stderr, "%s,\n", lua_tostring(L, -1)); lua_close(L); return 1; }
这句话便是开始执行lua函数栈上的chunk,参数为刚才push进去的config_file,那么chunk做了哪些事情呢?
local f = assert(io.open(config_name))\
local code = assert(f:read \'*a\')\
首先打开配置文件,并读取所有内容,接下来定义了一个函数:
local function getenv(name) return assert(os.getenv(name), \'os.getenv() failed: \' .. name) end\
这个函数执行返回 os.getenv(name), 也就是name为环境变量的名称,这里得到其值例如: print(os.getenv("HOME")),便会输出$(HOME)对应的值,
接下来:code = string.gsub(code, \'%$([%w_%d]+)\', getenv)\
可以看到用string.gsub来讲code(配置文件内容)中的$(...)环境变量用getenv中得到的来替换
.但是skynet中得config 没有类似的环境变量,所以得到的code没有变化.
接下来
f:close()\
local result = {}\
assert(load(code,\'=(load)\',\'t\',result))()\
return result\
关闭配置文件,将code load到result表中,然后返回result表,
我们注意到我们调用lua_pcall(L, 1,1,0),因此,result表此时在栈顶的位置. 到此为止,配置文件的内容已经存放在result表中,并且这个表在lua调用栈的栈顶
,接下来便是从栈上的表中读取表中的内容,然后存放到env中: 紧接着调用
_init_env(L);
static void _init_env(lua_State *L) { lua_pushnil(L); while(lua_next(L, -2) != 0) { 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); env_setenv(key, b ? "true" : "false"); } else { const char *value = lua_tostring(L, -1); if (value == NULL) { fprintf(stderr, "Invalud config table key = %s\n", key); exit(1); } env_setenv(key, value); } lua_pop(L, 1); } lua_pop(L, 1); }
这个函数是一个典型的读取table中以字符串作为键的table的内容,尤其是不知道table中的具体内容是什么的时候.
这里我们只知道是以键值对形式,且键是字符串,table为非数组。
上边我们知道此时lua函数栈的栈顶为存放了配置文件内容的table,那么这个函数就是挨个遍历table内容,并调用env_env_set()来存放。
首先lua_next(L, -2)先将栈顶元素弹出然后将table中的一个键值对放到栈上,键在-2位置上,值在-1位置上。因为lua_next(L, -2)先弹栈顶元素,因此在调用之前先pushnil,放进一个nil在栈顶,调用lua_next() nil 被弹出 然后table中的第一个键值对依次放到栈上,然后获得键值,调用 env_setenv()来存放内容到E。循环读取table的值,直到读完跳出循环。至此,config文件中的内容全部存放到env中的全局变量E中的虚拟机中,并可在其他地方调用来获得配置文件内容. 最后: init_conf(&conf); test_env(); lua_close(L); 其实就是仿照skynet_main.c来测试,test_env()可以替换成skynet_start(&conf)来继续启动skynet,这里简单的用来测试。 到此为止,读取config配置文件的工作就做完了,并保存到了env.c中的局部静态变量里。供其它地方使用配置。
基础有限,研究skynet时间有限,难免讲述不清或出现错误,望指正。 最后贴出全部测试文件代码:
----------------------------------------------------------------------------------------------------------
start.c, env.c的代码见上
env.h
#ifndef __ENV_H_ #define __ENV_H_ const char* env_getenv(const char*key); void env_setenv(const char *key, const char *value); void env_env_init(); #endif
spinlock.h
#ifndef SKYNET_SPINLOCK_H #define SKYNET_SPINLOCK_H #define SPIN_INIT(q) spinlock_init(&(q)->lock); #define SPIN_LOCK(q) spinlock_lock(&(q)->lock); #define SPIN_UNLOCK(q) spinlock_unlock(&(q)->lock); #define SPIN_DESTROY(q) spinlock_destroy(&(q)->lock); #ifndef USE_PTHREAD_LOCK struct spinlock { int lock; }; static inline void spinlock_init(struct spinlock *lock) { lock->lock = 0; } static inline void spinlock_lock(struct spinlock *lock) { while (__sync_lock_test_and_set(&lock->lock,1)) {} } static inline int spinlock_trylock(struct spinlock *lock) { return __sync_lock_test_and_set(&lock->lock,1) == 0; } static inline void spinlock_unlock(struct spinlock *lock) { __sync_lock_release(&lock->lock); } static inline void spinlock_destroy(struct spinlock *lock) { (void) lock; } #else #include <pthread.h> // we use mutex instead of spinlock for some reason // you can also replace to pthread_spinlock struct spinlock { pthread_mutex_t lock; }; static inline void spinlock_init(struct spinlock *lock) { pthread_mutex_init(&lock->lock, NULL); } static inline void spinlock_lock(struct spinlock *lock) { pthread_mutex_lock(&lock->lock); } static inline int spinlock_trylock(struct spinlock *lock) { return pthread_mutex_trylock(&lock->lock) == 0; } static inline void spinlock_unlock(struct spinlock *lock) { pthread_mutex_unlock(&lock->lock); } static inline void spinlock_destroy(struct spinlock *lock) { pthread_mutex_destroy(&lock->lock); } #endif #endif
config
root = "./" thread = 8 --logger = nil logger = "userlog" logservice = "snlua" --logservice = "catlogger" logpath = "./../../log/cat/" harbor = 1 address = "127.0.0.1:2401" master = "127.0.0.1:2013" start = "main" -- main script bootstrap = "snlua bootstrap" -- The service for bootstrap standalone = "0.0.0.0:2013" luaservice = "./../../service/logind/?.lua;./../../service/db/?.lua;./../../service/web/?.lua;./../../service/cat/?.lua;./../../service/?.lua;./service/?.lua" lualoader = "lualib/loader.lua" preload = "./../../lualib/preload.lua" -- run preload.lua before every lua service run snax = root.."../logind/?.lua;"..root.."test/?.lua" -- snax_interface_g = "snax_g" cpath = "./../../cservice/?.so;./cservice/?.so" -- daemon = "./skynet.pid"
makefile:
CC= gcc CFLAGS= -g -O2 -Wall LUA_INC= /usr/include/lua5.2 all: main main : start.c env.c env.h spinlock.h $(CC) $(CFLAGS) -o $@ $^ -I$(LUA_INC) -llua5.2 -lpthread -lm clean : rm -f main