
skynet 是 C 语言写的框架,我们采用学习过程中最基本的方式去阅读 skynet,从 C 语言的 main 函数开始。
首先我们找到框架的入口main函数,在 skynet/skynet-src/skynet_main.c 文件内。
main 函数的代码如下:
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; } skynet_globalinit(); skynet_env_init(); sigign(); struct skynet_config config; #ifdef LUA_CACHELIB // init the lock of code cache luaL_initcodecache(); #endif struct lua_State *L = luaL_newstate(); luaL_openlibs(L); // link lua lib int err = luaL_loadbufferx(L, load_config, strlen(load_config), "=[skynet config]", "t"); 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); config.thread = optint("thread",8); config.module_path = optstring("cpath","./cservice/?.so"); config.harbor = optint("harbor", 1); config.bootstrap = optstring("bootstrap","snlua bootstrap"); config.daemon = optstring("daemon", NULL); config.logger = optstring("logger", NULL); config.logservice = optstring("logservice", "logger"); config.profile = optboolean("profile", 1); lua_close(L); skynet_start(&config); skynet_globalexit(); return 0; } 我们一段一段查看
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; } //... } 定义了一个指针, 指针指向常量, const char* config_file, config_file 赋值为启动时的第二个参数,也就是配置文件的路径。
skynet_globalinit();
// skynet/skynet-src/skynet_server.c struct skynet_node { ATOM_INT total; int init; uint32_t monitor_exit; pthread_key_t handle_key; bool profile; // default is off }; static struct skynet_node G_NODE; void skynet_globalinit(void) { ATOM_INIT(&G_NODE.total , 0); G_NODE.monitor_exit = 0; G_NODE.init = 1; if (pthread_key_create(&G_NODE.handle_key, NULL)) { fprintf(stderr, "pthread_key_create failed"); exit(1); } // skynet/skynet-src/skynet_imp.h /* #define THREAD_WORKER 0 #define THREAD_MAIN 1 #define THREAD_SOCKET 2 #define THREAD_TIMER 3 #define THREAD_MONITOR 4 */ skynet_initthread(THREAD_MAIN); } skynet_initthread(int m) { // skynet/skynet-src/atomic.h // #define ATOM_POINTER volatile uintptr_t uintptr_t v = (uint32_t)(-m); pthread_setspecific(G_NODE.handle_key, (void *)v); } 初始化全局节点信息,total 为 0,monitor_exit 为 0,init 1,
pthread_key_create(&G_NODE.handle_key, NULL) 创建了一个多线程私有数据 handle_key,可参考文章: https://www.jianshu.com/p/d78d93d46fc2
skynet_initthread(THREAD_MAIN); 将当前线程状态由 THREAD_MAIN 切换为 THREAD_WORKER 状态并记录在 handle_key 。
skynet_env_init();
// skynet/skynet-src/skynet_env.c struct skynet_env { struct spinlock lock; lua_State *L; }; static struct skynet_env *E = NULL; void skynet_env_init() { E = skynet_malloc(sizeof(*E)); SPIN_INIT(E) E->L = luaL_newstate(); } E 一个skynet_env结构体,结构体包含一个 spinlock 自旋锁,一个 lua 虚拟机指针。
skynet_malloc 为结构体 E 分配内存,skynet_malloc内部暂时不细究。
SPIN_INIT(E)
通过查找代码得知, 这是在 skynet/skynet-src/spinlick.h 中定义的一个宏。
#define SPIN_INIT(q) spinlock_init(&(q)->lock);
对 E 中的 lock 进行初始化。
E->L = luaL_newstate(); L 绑定了一个 lua 虚拟机。
sigign();
#include <signal.h> int sigign() { struct sigaction sa; sa.sa_handler = SIG_IGN; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sigaction(SIGPIPE, &sa, 0); return 0; } main 函数同文件下的 sigign() 函数。
定义了一个 sigaction 结构体,将 sa_handler 设置为 SIG_IGN,表示要忽略信号的产生的动作。
sigaction(SIGPIPE, &sa, 0); 将 SIGPIPE 的行为替换为 sa 结构体定义的形式,表示当前进程忽略 SIGPIPE 信号。
这里简单记录了一下 sigaction 的资料。 01ext_sigaction
struct skynet_config config;
定义了结构体 config
struct skynet_config { int thread; int harbor; int profile; const char * daemon; const char * module_path; const char * bootstrap; const char * logger; const char * logservice; }; luaL_initcodecache();
// skynet/skynet-src/skynet_main.c #ifdef LUA_CACHELIB luaL_initcodecache(); #endif // skynet/3rd/lauxlib.c static struct codecache CC; struct codecache { struct spinlock lock; lua_State *L; }; LUALIB_API void luaL_initcodecache(void) { SPIN_INIT(&CC); } static const char * load_cOnfig= "\ local result = {}\n\ local function getenv(name) return assert(os.getenv(name), [[os.getenv() failed: ]] .. name) end\n\ local sep = package.config:sub(1,1)\n\ local current_path = [[.]]..sep\n\ 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\ 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(config_name)\n\ setmetatable(result, nil)\n\ return result\n\ "; struct lua_State *L = luaL_newstate(); luaL_openlibs(L); // link lua lib int err = luaL_loadbufferx(L, load_config, strlen(load_config), "=[skynet config]", "t"); 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; } luaL_loadbufferx(L, load_config, strlen(load_config), "=[skynet config]", "t");
加载了一段 lua 代码到内存里,并压入 lua 栈内。
load_config 这段代码实现的功能: 将配置文件内的 $var 替换成了环境变量的内容, 并返回了一个 result 表。
lua_pcall(L, 1, 1, 0);
执行压入的 load_config 代码块,第二个参数 1 表示压入的栈的个数为 1,lua_pushstring(L, config_file); 被压栈的配置文件名。 执行完函数之后,函数和参数自动出栈,此时栈为空。 函数的返回值被压栈,此时栈内只有一个表 result, result 内包含了配置在 config_file 内的键值对。
_init_env(L);
static void _init_env(lua_State *L) { lua_pushnil(L); /* first key */ 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); 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); } skynet_setenv(key,value); } lua_pop(L,1); } lua_pop(L,1); } // skynet/skynet-src/skynet_env.c void skynet_setenv(const char *key, const char *value) { SPIN_LOCK(E) lua_State *L = E->L; lua_getglobal(L, key); assert(lua_isnil(L, -1)); lua_pop(L,1); lua_pushstring(L,value); lua_setglobal(L,key); SPIN_UNLOCK(E) } // 从堆栈上弹出一个值,并将其设为全局变量 name 的新值。 void lua_setglobal (lua_State *L, const char *name); // 把全局变量 name 里的值压栈,返回该值的类型。 int lua_getglobal (lua_State *L, const char *name); 将 lua 栈表内的键值对设置到 &E->L 的全局环境中。
config.thread = optint("thread",8); config.module_path = optstring("cpath","./cservice/?.so"); config.harbor = optint("harbor", 1); config.bootstrap = optstring("bootstrap","snlua bootstrap"); config.daemon = optstring("daemon", NULL); config.logger = optstring("logger", NULL); config.logservice = optstring("logservice", "logger"); config.profile = optboolean("profile", 1); static int optint(const char *key, int opt) { const char * str = skynet_getenv(key); if (str == NULL) { char tmp[20]; sprintf(tmp,"%d",opt); skynet_setenv(key, tmp); return opt; } return strtol(str, NULL, 10); } // skynet/skynet-src/skynet_env.c const char * skynet_getenv(const char *key) { SPIN_LOCK(E) lua_State *L = E->L; lua_getglobal(L, key); const char * result = lua_tostring(L, -1); lua_pop(L, 1); SPIN_UNLOCK(E) return result; } optint, optstring, optboolean 从 &E->L 的全局环境中取得对应键的值,如果全局环境内未定义,则第二个参数 opt 设为 key 的默认值。
lua_close(L);
关闭 main 函数内创建的 lua 虚拟机。
skynet_start(&config);
下一节的内容。
skynet_globalexit();
void skynet_globalexit(void) { pthread_key_delete(G_NODE.handle_key); } 删除在 skynet_initthread 中定义的特殊的线程数据。
1 orange May 12, 2021 支持一下 |
3 join May 12, 2021 via iPhone 挺优美的,我也喜欢读。不搞游戏从来没用过。 |
4 zhengxiaowai May 12, 2021 源码阅读文章不是那么写的,大段的贴代码,没啥意义 应该抽取核心流程,删除防御检查类代码,5-10 行最好,配置详细的文字说明,实在简单就略过 |
5 yiouejv OP @zhengxiaowai 我就是想要一句句详细解读啊 |
6 zhengxiaowai May 12, 2021 |
7 yiouejv OP @zhengxiaowai 我倒是觉得很清晰哦,阅读源码没有具体的写法公式吧 |
8 orange May 13, 2021 建议画一些图,对整体有更清晰的描述 |
10 b00tyhunt3r May 13, 2021 感谢楼主!同时赞同画图可以加深理解 我看源码肯定要备一个笔记本的 其实蛮好奇云风为什么选择 c 语言写这个架构 |
11 paoqi2048 May 13, 2021 @b00tyhunt3r 他是 C 厨 |
12 helllllloworld May 13, 2021 @b00tyhunt3r 可以看看云风早些年的 blog 就明白了 |
13 luoqeng May 13, 2021 skynet 这种单机服务不怎么值得研究,现在的发展方向 微服务 service-mesh 之类的技术 |