1-skynet基本概况

新入门skynet系列视频b站网址 https://www.bilibili.com/video/BV19d4y1678X 视频中的讲解和本系列博客是对应的,但不是完全相同的.

新入门skynet系列博客的大纲

这一节主要是建立大致印象。暂时不要太纠结于细节

image-20220529093208589

问题:

  • 一个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整体的简单理解

b站对应视频 https://www.bilibili.com/video/BV19d4y1678X

posted @ 2022-12-08 14:22  程序员阿钢  阅读(1454)  评论(0编辑  收藏  举报