Skynet学习
skynet 节点通过运行 skynet 主程序启动,必须在启动命令行传入一个 Config 文件名作为启动参数。skynet 会读取这个 config 文件获得启动需要的参数。
./skynet ./examples/config
第一个启动的服务是 logger ,bootstrap 这个配置项关系着 skynet 运行的第二个服务。
在skynet-src:skynet_main.c中
int
main(int argc, char *argv[]) {
const char * config_file = NULL ;//定义一个const char*指针
if (argc > 1) {
config_file = argv[1];//指向输入的./examples/config
} 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();//lua虚拟机
luaL_openlibs(L); // link lua lib
int err = luaL_loadbufferx(L, load_config, strlen(load_config), "=[skynet config]", "t");//链接了config的lua脚本
assert(err == LUA_OK);
lua_pushstring(L, config_file);//string压栈
err = lua_pcall(L, 1, 1, 0);//config的lua脚本运行
if (err) {
fprintf(stderr,"%s\n",lua_tostring(L,-1));
lua_close(L);
return 1;
}
_init_env(L);//初始化环境配置
//初始化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);
lua_close(L);
//启动log服务 启动snlua服务 启动socket线程 启动minitor线程 启动worker线程(有weight权重影响)
skynet_start(&config);
skynet_globalexit();
return 0;
}
下面剖析skynet_start(&config)函数
在skynet-src:skynet_start.c中
void
skynet_start(struct skynet_config * config) {
// register SIGHUP for log file reopen
struct sigaction sa;
sa.sa_handler = &handle_hup;
sa.sa_flags = SA_RESTART;
sigfillset(&sa.sa_mask);
sigaction(SIGHUP, &sa, NULL);
if (config->daemon) {
if (daemon_init(config->daemon)) {
exit(1);
}
}
skynet_harbor_init(config->harbor);//初始化harbor
skynet_handle_init(config->harbor);//初始化handle
skynet_mq_init();//初始化message queue
skynet_module_init(config->module_path);//默认是./cservice/?.so 详情见config
skynet_timer_init();//初始化timer
skynet_socket_init();//初始化socket
skynet_profile_enable(config->profile);
struct skynet_context *ctx = skynet_context_new(config->logservice, config->logger);//开启log服务
if (ctx == NULL) {
fprintf(stderr, "Can't launch %s service\n", config->logservice);
exit(1);
}
skynet_handle_namehandle(skynet_context_handle(ctx), "logger");
bootstrap(ctx, config->bootstrap);//bootstrap主要启动了snlua服务
start(config->thread);//启动socket线程 启动minitor线程 启动worker线程(有weight权重影响)
// harbor_exit may call socket send, so it should exit before socket_free
skynet_harbor_exit();
skynet_socket_free();
if (config->daemon) {
daemon_exit(config->daemon);
}
}
下面剖析bootstrap(ctx, config->bootstrap)函数
在skynet-src:skynet_start.c中
static void
bootstrap(struct skynet_context * logger, const char * cmdline) {
int sz = strlen(cmdline);//cmdline一般是snlua bootstrap
char name[sz+1];
char args[sz+1];
sscanf(cmdline, "%s %s", name, args);//name是snlua args是bootstrap
struct skynet_context *ctx = skynet_context_new(name, args);//初始化snlua服务
if (ctx == NULL) {
skynet_error(NULL, "Bootstrap error : %s\n", cmdline);
skynet_context_dispatchall(logger);
exit(1);
}
}
剖析skynet_context_new(name, args)函数
在skynet-src:skynet_server.c中
struct skynet_context *
skynet_context_new(const char * name, const char *param) {
struct skynet_module * mod = skynet_module_query(name);//通过name去加载模块.so文件 在这里是snlua.so文件 加载成skynet的模块(create init signal release加载这四个函数指针 赋值给模块)
if (mod == NULL)
return NULL;
void *inst = skynet_module_instance_create(mod);//调用模块的create函数
if (inst == NULL)
return NULL;
struct skynet_context * ctx = skynet_malloc(sizeof(*ctx));//定义一个skynet_context指针
CHECKCALLING_INIT(ctx)
//复制给ctx
ctx->mod = mod;
ctx->instance = inst;
ctx->ref = 2;
ctx->cb = NULL;
ctx->cb_ud = NULL;
ctx->session_id = 0;
ctx->logfile = NULL;
ctx->init = false;
ctx->endless = false;
ctx->cpu_cost = 0;
ctx->cpu_start = 0;
ctx->message_count = 0;
ctx->profile = G_NODE.profile;
// Should set to 0 first to avoid skynet_handle_retireall get an uninitialized handle
ctx->handle = 0;
ctx->handle = skynet_handle_register(ctx);//生成唯一的handle标记
struct message_queue * queue = ctx->queue = skynet_mq_create(ctx->handle);//初始化message queue 记录了handle
// init function maybe use ctx->handle, so it must init at last
context_inc();//context的数量自增1
CHECKCALLING_BEGIN(ctx)
int r = skynet_module_instance_init(mod, inst, ctx, param);//调用module的init函数
CHECKCALLING_END(ctx)
if (r == 0) {
struct skynet_context * ret = skynet_context_release(ctx);//这里使用引用计数管理内存 调用release函数 释放mq 释放ctx context数量自减
if (ret) {
ctx->init = true;
}
skynet_globalmq_push(queue);//压入全局message queue
if (ret) {
skynet_error(ret, "LAUNCH %s %s 1", name, param ? param : "");
}
return ret;
} else {
skynet_error(ctx, "FAILED launch %s", name);
uint32_t handle = ctx->handle;
skynet_context_release(ctx);
skynet_handle_retire(handle);
struct drop_t d = { handle };
skynet_mq_release(queue, drop_message, &d);
return NULL;
}
}
如上链接了.so库 以及入消息队列
主要有如下.so文件链接
find ./ -name "*.so"
./cservice/gate.so
./cservice/snlua.so
./cservice/harbor.so
./cservice/logger.so
./3rd/jemalloc/lib/libjemalloc.so
./luaclib/bson.so
./luaclib/client.so
./luaclib/sproto.so
./luaclib/lpeg.so
./luaclib/md5.so
./luaclib/skynet.so
下面剖析start(config->thread);//启动socket线程 启动minitor线程 启动worker线程(有weight权重影响)
在在skynet-src:skynet_start.c中
static void
start(int thread) {
pthread_t pid[thread+3];//3是因为monitor socket timer线程
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 *));
int i;
for (i=0;i<thread;i++) {
m->m[i] = skynet_monitor_new();//定义skynet_monitor数组 数量为thread个
}
if (pthread_mutex_init(&m->mutex, NULL)) {//初始和mutex
fprintf(stderr, "Init mutex error");
exit(1);
}
if (pthread_cond_init(&m->cond, NULL)) {//初始化cond
fprintf(stderr, "Init cond error");
exit(1);
}
create_thread(&pid[0], thread_monitor, m);//设置thread_monitor
create_thread(&pid[1], thread_timer, m);//设置thread_timer
create_thread(&pid[2], thread_socket, m);//设置thread_socket
static int weight[] = { //权重
-1, -1, -1, -1, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, };
struct worker_parm wp[thread];
for (i=0;i<thread;i++) {
wp[i].m = m;
wp[i].id = i;
if (i < sizeof(weight)/sizeof(weight[0])) {
wp[i].weight= weight[i];
} else {
wp[i].weight = 0;
}
create_thread(&pid[i+3], thread_worker, &wp[i]);//设置work thread
}
for (i=0;i<thread+3;i++) {
pthread_join(pid[i], NULL);
}
free_monitor(m);
}
下面主要剖析work thread
在在skynet-src:skynet_start.c中
static void *
thread_worker(void *p) {
struct worker_parm *wp = p;//转换传入的p 在create传入
int id = wp->id;//id weight monitor skynet_monitor message_queue赋值
int weight = wp->weight;
struct monitor *m = wp->m;
struct skynet_monitor *sm = m->m[id];
skynet_initthread(THREAD_WORKER);
struct message_queue * q = NULL;
while (!m->quit) {
q = skynet_context_message_dispatch(sm, q, weight);//消息转发
if (q == NULL) {
if (pthread_mutex_lock(&m->mutex) == 0) {
++ m->sleep;
// "spurious wakeup" is harmless,
// because skynet_context_message_dispatch() can be call at any time.
if (!m->quit)
pthread_cond_wait(&m->cond, &m->mutex);
-- m->sleep;
if (pthread_mutex_unlock(&m->mutex)) {
fprintf(stderr, "unlock mutex error");
exit(1);
}
}
}
}
return NULL;
}
下面见skynet_context_message_dispatch(sm, q, weight);函数
在skynet-src:skynet_server.c中
struct message_queue *
skynet_context_message_dispatch(struct skynet_monitor *sm, struct message_queue *q, int weight) {
if (q == NULL) {//进入时一般为空 不为空是因为sleep过
q = skynet_globalmq_pop();//获取全局message queue的一个数据
if (q==NULL)//全局消息都为空
return NULL;
}
uint32_t handle = skynet_mq_handle(q);//获取唯一handle
struct skynet_context * ctx = skynet_handle_grab(handle);//根据handle获取context 并且引用计数减一
if (ctx == NULL) {
struct drop_t d = { handle };
skynet_mq_release(q, drop_message, &d);
return skynet_globalmq_pop();
}
int i,n=1;
struct skynet_message msg;
for (i=0;i<n;i++) {
if (skynet_mq_pop(q,&msg)) {//message出一个数据
skynet_context_release(ctx);
return skynet_globalmq_pop();
} else if (i==0 && weight >= 0) {//根据权重改变n 循环次数
n = skynet_mq_length(q);//根据长度和weight改变循环次数
n >>= weight;
}
int overload = skynet_mq_overload(q);//是否过长
if (overload) {
skynet_error(ctx, "May overload, message queue length = %d", overload);
}
skynet_monitor_trigger(sm, msg.source , handle);//触发监视器 并且版本号自增
if (ctx->cb == NULL) {
skynet_free(msg.data);
} else {
dispatch_message(ctx, &msg);//分发消息
}
skynet_monitor_trigger(sm, 0,0);//重制source 和 destination
}
assert(q == ctx->queue);
struct message_queue *nq = skynet_globalmq_pop();
if (nq) {
// If global mq is not empty , push q back, and return next queue (nq)
// Else (global mq is empty or block, don't push q back, and return q again (for next dispatch)
skynet_globalmq_push(q);//这一句将q重新放入全局message queue不知道为什么
q = nq;//这个跳过函数开头的空判断
}
skynet_context_release(ctx);
return q;
}
下面剖析 dispatch_message(ctx, &msg);//分发消息 通过回调函数分发消息
static void
dispatch_message(struct skynet_context *ctx, struct skynet_message *msg) {
assert(ctx->init);
CHECKCALLING_BEGIN(ctx)
pthread_setspecific(G_NODE.handle_key, (void *)(uintptr_t)(ctx->handle));
int type = msg->sz >> MESSAGE_TYPE_SHIFT;
size_t sz = msg->sz & MESSAGE_TYPE_MASK;
if (ctx->logfile) {
skynet_log_output(ctx->logfile, msg->source, type, msg->session, msg->data, sz);//日志输出
}
++ctx->message_count;
int reserve_msg;
if (ctx->profile) {
ctx->cpu_start = skynet_thread_time();
reserve_msg = ctx->cb(ctx, ctx->cb_ud, type, msg->session, msg->source, msg->data, sz);//回调
uint64_t cost_time = skynet_thread_time() - ctx->cpu_start;
ctx->cpu_cost += cost_time;//记录消耗时长
} else {
reserve_msg = ctx->cb(ctx, ctx->cb_ud, type, msg->session, msg->source, msg->data, sz);//触发回调
}
if (!reserve_msg) {
skynet_free(msg->data);
}
CHECKCALLING_END(ctx)
}