|
skynet各层表现
从上大概就清楚skynet的“内部”
而skynet源码目录结构如下:
3rd:第三方代码,有lua和jemalloc等。
lualib:使用lua写的库
lualib-src:使用C写并封装给lua使用的库
service:使用lua写的skynet的服务模块
service-src:使用C写的skynet的服务模块
skynet-src:skynet核心代码
skynet重要模块gate.so:为整个skynet提供socket功能
snlua.so:启动多个lua服务,skynet自带的模块中有一个重要的模块是snlua.so模块,通过snlua.so和指定lua脚本文件可以启动多个lua编写的服务,不用每个服务都是用c来编写,而且大部分逻辑都是在 lua 脚本下开发,只有需要考虑性能的模块才用 C 语言开发成库,直接提供给 lua 调用。
logger.so:日志模块,一个简单的日志系统,可以用来记录服务的相关信息。
harbar.so:(集群模块)节点服务,每个Skynet运行都是一个节点
skynet重要服务launcher.lua:在lua中启动服务
skynet.lua:lua常用功能封装
skynet.so:lua调用skynet功能
skynet重要文件skynet_server.c:管理服务
skynet_handle.c:管理服务唯一的handle
skynet_module.c:启动c编写的so模块
skynet_monitor.c:监视服务死循环
skynet_mq.c:消息队列
skynet_timer.c:定时器
skynet_socket.c:Socket
skynet_master.c:不同skynet节点服务名字中心服务
skynet_harbor.c:不同skynet节点通讯
skynet底层代码位于skynet/skynet-src下,模块加载相关在skynet-module.c skynet-module.h这两个文件里。这里的模块在linux下指的是so,在windows下指的是dll,在skynet中指的是config中配置的cpath下的文件。
每个模块需要实现四个最基本的函数,create/init/release/signal。create做内存分配。init做初始化,它可能会做一些其它的事情,比如打开网络,打开文件,函数回调挂载等等。relase做资源回收,包括内存资源,文件资源,网络资源等等,signal是发信号,比如kill信号,告诉模块该停了。
网络网络部分是一个服务器最基础最核心的部分,这个技术也已经是非常成熟了,现在已经很少有人自己实现一个网络相关的库了。skynet的网络库是自己实现的。
实际上云风只实现了epoll和kqueue,windows上的变种请自行搜索吧。
epoll和kqueue的实现分别在skynet_epoll.h和epoll_kqueue.h当中。epoll的函数其实就是epoll_create/epoll_ctl/epoll_del/epoll_wait这几个,要注意的是skynet中的epoll_create的参数是1024。所以连接数上不去的话很可能就是这里限制了。
skynet在skynet_poll.h中根据平台的不同包含了不同的头文件,屏蔽了平台相关性。然后在socket_server.c中实现了网络服务的逻辑。
然后skynet在skynet_socket.c中对socket_server.c中的逻辑再次做了一个封装,还添加了socket客户端相关的函数,就是connect/send/close之类的函数。
为了方便lua层使用socket,在lua-socket.c中再将对skynet_socket.c进行了一次封装。这个封装就是c语言层和lua语言层的相互转换。目前只支持tcp和udp,基于tcp上的http/websocket之类统统是不支持的。
为什么操作一个网络要费这么大的劲呢,绕来绕去非常的不直观。因为skynet是基于消息的,而且每个服务都有一个monitor,每个消息处理的时候要尽可能的短,这样才不会阻塞服务里其它的请求。而connect这种明显是阻塞的,当然也可以写成非阻塞的,但是非阻塞的话,你需要不断地挂起,因为非阻塞实际上是基于select技术来实现的。而不断地挂起,这个就很麻烦,写起来很痛苦而且很容易出错。因此云风把这些都放到网络线程中来做,这样就不会影响工作线程。但是这样做也有它的缺点,那就是网络线程可能会被阻塞,网络线程被阻塞就会导致服务无响应。或者导致大量的数据包积累,引起波峰。
skynet的配置加载
skynet的配置文件是以lua格式来写的。使用过skynet的都清楚skynet的启动命令是skynet config_file_name。配置文件名是作为命令行参数传给skynet进程的。
skynet进程启动以后,会读取config文件,然后解析这个lua文件。然后把相关的配置信息设置到lua的环境变量里。
C层读取配置的话是要从lua环境变量里去取的。
skynet一共有4种线程,monitor线程用于检测节点内的消息是否堵住,timer线程运行定时器,socket线程进行网络数据的收发,worker线程则负责对消息队列进行调度(worker线程的数量,可以通过配置表指定)。消息调度规则是,每条worker线程,每次从全局消息队列中pop出一个次级消息队列,并从次级消息队列中pop出一条消息,并找到该次级消息队列的所属服务,将消息传给该服务的callback函数,执行指定业务,当逻辑执行完毕时,再将次级消息队列push回全局消息队列中。因为每个服务只有一个次级消息队列,每当一条worker线程,从全局消息队列中pop出一个次级消息队列时,其他线程是拿不到同一个服务,并调用callback函数,因此不用担心一个服务同时在多条线程内消费不同的消息,一个服务执行,不存在并发,线程是安全的
socket线程、timer线程甚至是worker线程,都有可能会往指定服务的次级消息队列中push消息,push函数内有加一个自旋锁,避免同时多条线程同时向一个次级消息队列push消息的惨局。
在2021年1月13/14号我会开一个四小时玩转skynet训练营
现在已经开放报名,感兴趣的诸位同好可以订阅一下,
训练营内容大概如下:
1. 多核并发编程
2. 消息队列,线程池
3. actor消息调度
4. 网络模块实现
5. 时间轮定时器实现
6. lua/c接口编程
7. skynet编程精要
8. demo演示actor编程思维
期待与诸位同好共襄技术盛举
凭借报名截图可以进群973961276领取上一期skynet训练营的录播以及这期的预习资料哦!
|
|