1-skynet基本概况
新入门skynet系列视频b站网址 https://www.bilibili.com/video/BV19d4y1678X 视频中的讲解和本系列博客是对应的,但不是完全相同的.
这一节主要是建立大致印象。暂时不要太纠结于细节
问题:
-
一个skynet进程里面总共有什么线程
-
工作线程怎么工作的
-
私有队列消息从哪里来的
//一个工作线程主要工作流程
while(true)
{
queue = get_one_queue(global_queue) //通过全局队列拿到一个私有队列
ctx = get_ctx( queue->handle ) //通过队列拿到一个服务
msg = get_one_message(queue) //从队列里面取出一个消息
ctx->callback(msg) //调用服务的回调函数 ,在这个回调函数里面再调用一个固定的 lua函数
}
一个skynet节点,也就是一个skynet进程。他启动的时候会创建多个线程,他们是
- 网络线程
- 定时器线程
- 监听线程
- 多个工作线程
上图中所示的每个队列其实都是关联一个服务的。所有的队列连接成一个大链表。服务的概念你可以对比医院来理解。比如今天有三个主治医生坐诊,他们都是看痔疮的,名字是张三李四王五。每个患者都在大厅等候,医生叫号的时候,就进去看病。比如你预约的张三医生,你的挂号单上面可能显示前面还有好几个人,也就是你要排队等着。那么张三医生叫你的时候,你就去他的问诊室看病。
这里张三医生就是一个服务。服务关联的队列就是排队找他看痔疮的同学。李四医生也是一个服务。张三和李四他们是没有关联的,各看各的患者。当然李四医生和张三医生本质上都是看痔疮的服务。需要多个服务的原因是,得痔疮的同学太多了。
再看看几个线程的主要功能
网络线程:
- 接收skynet外部的网络请求,然后push到服务的队列。
- 当服务需要把数据发送给外部时,实质上是 服务--->网络线程--->外部
定时器线程:把定时器消息push到某个服务。当服务在处理定时器消息时,就可以认为是定时器事件触发了。
监听线程:主要是发现工作线程有没有死循环。
工作线程:驱动服务处理消息
下面看代码。skynet进程的主函数入口是 skynet_main.c的main函数
int
main(int argc, char *argv[]) {
const char * config_file = NULL ;
skynet_globalinit();
skynet_env_init();
sigign();
struct skynet_config config;
struct lua_State *L = luaL_newstate();//生成luastate的目的主要是读取配置文件中的配置项
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);//执行配置文件
_init_env(L);
//把配置文件里面的配置项 都读出来
config.thread = optint("thread",8);
config.module_path = optstring("cpath","./cservice/?.so");//默认路径下的动态链接库有gate.so harbor.so logger.so snlua.so 常用snlua.so来创建模块实例
config.harbor = optint("harbor", 1);//这里推荐在配置文件中设置为0 否则默认为1
config.bootstrap = optstring("bootstrap","snlua bootstrap");//启动服务设置: snlua作为服务模块 bootstrap作为服务对应的lua文件
config.daemon = optstring("daemon", NULL);//后台运行配置
config.logger = optstring("logger", NULL);//保存日志的文件名字 这里日志的来源是lua层调用的skynet.error(str)
config.logservice = optstring("logservice", "logger");//默认日志模块是 logger 即 service_logger.c 所代表的模块
config.profile = optboolean("profile", 1);
lua_close(L);
skynet_start(&config);//next
skynet_globalexit();
return 0;
}
上面我们关注的是 main函数读取了一个 lua配置文件。这个配置文件就是你启动 skynet进程时使用的命令,类似 ./skynet config
。 这里的config就是配置文件。
void
skynet_start(struct skynet_config * config) {
skynet_harbor_init(config->harbor);
skynet_handle_init(config->harbor);//可以简单的认为就是保存了 handle和服务 映射关系的数组
skynet_mq_init();//初始化一个全局队列 用来把所有服务关联的队列 连接起来
skynet_module_init(config->module_path);//模块初始化
skynet_timer_init();
skynet_socket_init();
skynet_profile_enable(config->profile);
struct skynet_context *ctx = skynet_context_new(config->logservice, config->logger);//首先创建日志服务
skynet_handle_namehandle(skynet_context_handle(ctx), "logger");//给日志服务绑定一个名字
bootstrap(ctx, config->bootstrap);//创建bootstrp服务 注意这里服务并没有完成所有流程 因为工作线程还没有启动 服务的第一个消息只是push进队列了
start(config->thread);//next
}
上面的代码主要是做了初始化工作和启动了两个服务。后面再细说服务。我们来看 start(config->thread)
。这里主要就是启动了 监听线程 定时器线程 网络线程 和多个工作线程。
static void
start(int thread) {
pthread_t pid[thread+3];
struct monitor *m = skynet_malloc(sizeof(*m));//创建监听管理器
memset(m, 0, sizeof(*m));
m->count = thread;//数量跟工作线程数一致 工作线程数一般又设置成跟你的cpu核心数相同
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();//每个工作线程安排一个监听
}
create_thread(&pid[0], thread_monitor, m);//监听线程的职责是监听工作线程
create_thread(&pid[1], thread_timer, m);//定时器线程
create_thread(&pid[2], thread_socket, m);//网络线程
struct worker_parm wp[thread];
for (i=0;i<thread;i++) {
create_thread(&pid[i+3], thread_worker, &wp[i]);//给每个工作线程传递不同参数
}
}
到这里就算是完成了对skynet整体的简单理解