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 2021-05-12 21:08:49 +08:00
支持一下
|
3
join 2021-05-12 23:15:53 +08:00 via iPhone
挺优美的,我也喜欢读。不搞游戏从来没用过。
|
4
zhengxiaowai 2021-05-12 23:44:30 +08:00
源码阅读文章不是那么写的,大段的贴代码,没啥意义
应该抽取核心流程,删除防御检查类代码,5-10 行最好,配置详细的文字说明,实在简单就略过 |
5
yiouejv OP @zhengxiaowai 我就是想要一句句详细解读啊
|
6
zhengxiaowai 2021-05-12 23:52:25 +08:00
|
7
yiouejv OP @zhengxiaowai 我倒是觉得很清晰哦,阅读源码没有具体的写法公式吧
|
8
orange 2021-05-13 01:40:33 +08:00
建议画一些图,对整体有更清晰的描述
|
10
b00tyhunt3r 2021-05-13 09:41:37 +08:00
感谢楼主!同时赞同画图可以加深理解 我看源码肯定要备一个笔记本的
其实蛮好奇云风为什么选择 c 语言写这个架构 |
11
paoqi2048 2021-05-13 10:07:06 +08:00
@b00tyhunt3r 他是 C 厨
|
12
helllllloworld 2021-05-13 13:23:23 +08:00
@b00tyhunt3r 可以看看云风早些年的 blog 就明白了
|
13
luoqeng 2021-05-13 20:59:17 +08:00
skynet 这种单机服务不怎么值得研究,现在的发展方向 微服务 service-mesh 之类的技术
|