Skynet 是一个轻量级、高并发的服务器框架。它在启动时会进行一系列初始化操作,并启动多个不同功能的线程(Monitor、Timer、Worker、Socket),从而实现消息分发、定时器、网络I/O等核心功能。本文主要从 main() 函数开始一步步trace,循序渐进地看 Skynet 的启动过程以及各条线程的分工,为后续深入阅读 Skynet 源码做铺垫。
int
main(int argc, char *argv[]) {
// 1) 获取配置文件
const char * config_file = NULL ;
if (argc > 1) {
config_file = argv[1];
} else {
fprintf(stderr, "Need a config file. usage: skynet configfilename\n");
return 1;
}
// 2) Skynet全局初始化
skynet_globalinit();
skynet_env_init();
sigign(); // 忽略部分信号
struct skynet_config config;
// 3) 解析配置文件
struct lua_State *L = luaL_newstate();
luaL_openlibs(L);
// load_config 脚本读取 config_file 并将其返回到 Lua
// ...
_init_env(L); // 将Lua中的配置项保存到C层
lua_close(L);
// 4) 将 Lua 解析到的配置写入 config 结构体
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);
// 5) 启动Skynet
skynet_start(&config);
// 6) 全局退出清理
skynet_globalexit();
return 0;
}
流程要点:
config_file
。skynet_globalinit
/ skynet_env_init
设置一些全局环境,注册信号处理等。config_file
并存到 struct skynet_config
。skynet_start
:是 Skynet 的核心启动函数,后面会详细讲。skynet_globalexit
做一些释放资源操作(比如内存管理模块等)。从这里可以看到:Lua 脚本用于配置 Skynet,通过 Lua 作为配置及引擎脚本。
void
skynet_start(struct skynet_config * config) {
// 1) 注册SIGHUP用来重开日志文件
struct sigaction sa;
sa.sa_handler = &handle_hup;
sa.sa_flags = SA_RESTART;
sigfillset(&sa.sa_mask);
sigaction(SIGHUP, &sa, NULL);
// 2) 若配置了daemon模式 -> 后台运行
if (config->daemon) {
if (daemon_init(config->daemon)) {
exit(1);
}
}
// 3) 各模块初始化
skynet_harbor_init(config->harbor);
skynet_handle_init(config->harbor);
skynet_mq_init();
skynet_module_init(config->module_path);
skynet_timer_init();
skynet_socket_init();
skynet_profile_enable(config->profile);
// 4) 启动 logservice 服务
struct skynet_context *ctx = skynet_context_new(config->logservice, config->logger);
if (ctx == NULL) {
fprintf(stderr, "Can't launch %s service\n", config->logservice);
exit(1);
}
skynet_handle_namehandle(skynet_context_handle(ctx), "logger");
// 5) 启动 bootstrap 服务
bootstrap(ctx, config->bootstrap);
// 6) 启动多线程
start(config->thread);
// 7) 退出处理
skynet_harbor_exit();
skynet_socket_free();
if (config->daemon) {
daemon_exit(config->daemon);
}
}
skynet_harbor_init
:与分布式/harbor机制有关(分布式集群的一部分)。skynet_handle_init
:管理 “Handle -> Service” 映射。skynet_mq_init
:初始化全局消息队列结构(global_queue
)。skynet_module_init
:C服务的模块管理,如加载 cpath
下的 .so
。skynet_timer_init
:定时器初始化,创建 TI = timer_create_timer()
并记录当前系统时间。skynet_socket_init
:socket层初始化,创建 socket_server
。skynet_profile_enable
:若 config->profile
为真,打开性能分析。logger
服务)。start(config->thread);
static void start(int thread)
。skynet_harbor_exit
、skynet_socket_free
等操作收尾。static void
start(int thread) {
pthread_t pid[thread+3];
// 1) 创建 monitor 结构
struct monitor *m = skynet_malloc(sizeof(*m));
memset(m, 0, sizeof(*m));
m->count = thread;
m->sleep = 0;
m->m = skynet_malloc(thread * sizeof(struct skynet_monitor *));
// 初始化互斥量, 条件变量
// 2) 创建3条特殊线程
create_thread(&pid[0], thread_monitor, m);
create_thread(&pid[1], thread_timer, m);
create_thread(&pid[2], thread_socket, m);
// 3) 创建 worker 线程
struct worker_parm wp[thread];
for (i=0;i
由此可见:
thread_monitor
thread_timer
thread_socket
thread_worker
(数量 = thread
)总线程数 = thread + 3
。
static void *
thread_monitor(void *p) {
struct monitor * m = p;
...
for (;;) {
// 定期检查 worker 是否卡死
for (i=0;im[i]);
}
sleep(1);
}
return NULL;
}
skynet_monitor_check
观测 worker 执行时间。static void *
thread_timer(void *p) {
struct monitor * m = p;
for (;;) {
skynet_updatetime(); // 更新定时器
skynet_socket_updatetime();// socket层时间更新
wakeup(m,m->count-1); // 唤醒所有线程( 让 worker 从 cond.wait 中唤醒 )
usleep(2500); // 2.5 ms 间隔
...
}
...
return NULL;
}
usleep(2500)
=> 每 2.5ms tick 一次static void *
thread_socket(void *p) {
for (;;) {
int r = skynet_socket_poll();
if (r==0)
break;
if (r<0) {
continue;
}
wakeup(m,0);
}
return NULL;
}
skynet_socket_poll()
,监听网络事件(读写就绪、连接、断开等)。r==0
表示 socket server 退出 => breakwakeup
=> 唤醒 Worker 线程去处理消息static void *
thread_worker(void *p) {
struct worker_parm *wp = p;
while (!m->quit) {
// 1) 分发一条消息
q = skynet_context_message_dispatch(sm, q, weight);
if (q == NULL) {
// 如果没有消息可处理 => sleep
pthread_cond_wait(&m->cond, &m->mutex);
}
}
return NULL;
}
pthread_cond_wait
=> 休眠 => 等 Timer 或 Socket 线程唤醒。weight
让某些线程能多处理几条消息(可实现不均衡分配, 保证核心Worker多干活)。下图(示意)可帮助理解:
(1) main()
|
v
skynet_globalinit() + skynet_env_init()
|
+--> parse config via Lua
|
v
skynet_start(&config)
|--- register SIGHUP
|--- if daemon => daemon_init
|--- skynet_*_init:
| - harbor_init
| - handle_init
| - mq_init
| - module_init
| - timer_init
| - socket_init
|
|--- create logservice => "logger"
|--- bootstrap => e.g. "snlua bootstrap"
|
|--- start(#thread)
|
+-> create monitor thread
+-> create timer thread
+-> create socket thread
+-> create N worker threads
|
+-> all threads join => end
|
v
skynet_globalexit()
|
v
return
skynet_start
。skynet_start
。通过这样一个启动流程,Skynet 建立起一个消息驱动的并发系统框架:
后续若要深入,可阅读每个线程对应函数(如 thread_timer
, thread_worker
)里更细节的流程,比如如何 dispatch 消息,如何在定时器中移位管理远期任务等。本篇作为阅读 Skynet 源码的起点,建立对整个启动过程和多线程分工有一个全局认识。