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
複製代碼
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章