skynet源码分析之定时器skynet_timer.c
skynet自带定时器功能skynet-src/skynet_timer.c,在skynet启动时会创建一个线程专门跑定时器。每帧(0.0025秒/帧)调用skynet_updatetime()
1 // skynet-src/skynet_start.c2 3 create_thread(&pid[1], thread_timer, m);4 5 static void *6 thread_timer(void *p) {7 struct monitor * m = p;8 skynet_initthread(THREAD_TIMER);9 for (;;) {
10 skynet_updatetime();
11 CHECK_ABORT
12 wakeup(m,m->count-1);
13 usleep(2500); //2500微妙=0.0025秒
14 if (SIG) {
15 signal_hup();
16 SIG = 0;
17 }
18 }
19 ...
20 }
1. 设计思想
skynet的设计思想参考Linux内核动态定时器的机制,参考Linux动态内核定时器介绍http://www.cnblogs.com/leaven/archive/2010/08/19/1803382.html,
在skynet里,时间精度是0.01秒,这对于游戏服务器来说已经足够了,定义1滴答=0.01秒,1秒=100滴答。其核心思想是:每个定时器设置一个到期的滴答数,与当前系统的滴答数(启动时是0,然后1滴答1滴答往后跳)比较差值,如果差值interval比较小(0<=interval<=2^8-1),表示定时器即将到来,需要严格关注,把它们保存在2^8个定时器链表里;如果interval越大,表示定时器越远,可以不用太关注,划分成4个等级,2^8<=interval<=2^(8+6)-1,2^(8+6)<=interval<=2^(8+6+6),...,每个等级只需要2^6个定时器链表保存,比如对于2^8<=interval<=2^(8+6)-1的定时器,将interval>>8相同的值idx保存在第一个等级位置为idx的链表里。
这样做的优势是:不用为每一个interval创建一个链表,而只需要2^8+4*(2^6)个链表,大大节省了内存。
之后,在不同情况下,分配不同等级的定时器,等级越高,表示越遥远,需要重新分配的次数越少。
对于从事游戏客户端开发的朋友,界面开发很繁琐,客户端技术更新很快,很少能接触底层开发,想转型做游戏服务端开发的朋友,学习skynet可以了解服务器的整体架构流程,基础组件以及服务端的编程思维。
对于从事小游戏开发,skynet是一个轻量级游戏服务器框架,同时学习服务端,能进行全栈开发
关于Skynet这里给大家推荐一个学习视频,里面详细讲解了以下知识点内容,视频链接:https://ke.qq.com/course/2806743?flowToken=1030833
2. 源码分析
数据结构:timer->near,保存2^8个即将到来的定时器链表;timer->t,保存4个分级数组,数组的每一项是一个链表;timer->time保存从skynet启动到现在走过的滴答数
1 // skynet-src/skynet_timer.c2 struct timer_event {3 uint32_t handle;4 int session;5 };6 7 struct timer_node { //单个定时器节点8 struct timer_node *next;9 uint32_t expire; //到期滴答数
10 };
11
12 struct link_list { //定时器链表
13 struct timer_node head;
14 struct timer_node *tail;
15 };
16
17 struct timer {
18 struct link_list near[TIME_NEAR];
19 struct link_list t[4][TIME_LEVEL];
20 struct spinlock lock;
21 uint32_t time; //启动到现在走过的滴答数,等同于current
22 ...
23 };
调用skynet_timeout创建一个定时器
然后调用timer_add(第8行),给timer_node指针分配空间,timer_node结构里并没有timer_event字段,除了分配node自身大小外,额外再分配timer_event大小的空间用来存放event,之后通过node+1的位置可以获取到timer_event数据
然后调用add_node(第21行),添加到定时器链表里,如果定时器的到期滴答数跟当前比较近(<2^8),表示即将触发定时器添加到T->near数组里,否则根据差值大小添加到对应的T->T[i]中
1 // skynet-src/skynet_timer.c2 int3 skynet_timeout(uint32_t handle, int time, int session) {4 ...5 struct timer_event event;6 event.handle = handle;7 event.session = session;8 timer_add(TI, &event, sizeof(event), time);9
10 return session;
11 }
12
13 static void
14 timer_add(struct timer *T,void *arg,size_t sz,int time) {
15 struct timer_node *node = (struct timer_node *)skynet_malloc(sizeof(*node)+sz);
16 memcpy(node+1,arg,sz);
17
18 SPIN_LOCK(T);
19
20 node->expire=time+T->time;
21 add_node(T,node);
22
23 SPIN_UNLOCK(T);
24 }
25
26 static void
27 add_node(struct timer *T,struct timer_node *node) {
28 uint32_t time=node->expire;
29 uint32_t current_time=T->time;
30
31 if ((time|TIME_NEAR_MASK)==(current_time|TIME_NEAR_MASK)) {
32 link(&T->near[time&TIME_NEAR_MASK],node);
33 } else {
34 int i;
35 uint32_t mask=TIME_NEAR << TIME_LEVEL_SHIFT;
36 for (i=0;i<3;i++) {
37 if ((time|(mask-1))==(current_time|(mask-1))) {
38 break;
39 }
40 mask <<= TIME_LEVEL_SHIFT;
41 }
42
43 link(&T->t[i][((time>>(TIME_NEAR_SHIFT + i*TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK)],node);
44 }
45 }
每帧从T->near中触发到期的定时器链表,near数组里每一项的链表中的所有节点的到期滴答数是相同的。
调用dispatch_list进行分发,通过current+1获取timer_event数据(第18行),然后给event->handle push一条消息表示触发定时器(第25行)
1 // skynet-src/skynet_timer.c2 static inline void3 timer_execute(struct timer *T) {4 int idx = T->time & TIME_NEAR_MASK;5 6 while (T->near[idx].head.next) {7 struct timer_node *current = link_clear(&T->near[idx]);8 SPIN_UNLOCK(T);9 // dispatch_list don't need lock T
10 dispatch_list(current);
11 SPIN_LOCK(T);
12 }
13 }
14
15 static inline void
16 dispatch_list(struct timer_node *current) {
17 do {
18 struct timer_event * event = (struct timer_event *)(current+1);
19 struct skynet_message message;
20 message.source = 0;
21 message.session = event->session;
22 message.data = NULL;
23 message.sz = (size_t)PTYPE_RESPONSE << MESSAGE_TYPE_SHIFT;
24
25 skynet_context_push(event->handle, &message);
26
27 struct timer_node * temp = current;
28 current=current->next;
29 skynet_free(temp);
30 } while (current);
31 }
每帧除了触发定时器外,还需重新分配定时器所在区间(timer_shift),因为T->near里保存即将触发的定时器,所以每TIME_NEAR-1(2^8-1)个滴答数才有可能需要分配(第22行)。否则,分配T->t中某个等级即可。
当T->time的第8位不全为0时,不需要分配,所以每2^8个滴答数才有需要分配一次;
当T->time的第9-14位不全为0时,重新分配T[0]等级,每2^8个滴答数分配一次,idx从1开始,每次分配+1;
当T->time的第15-20位不全为0时,重新分配T[1]等级,每2^(8+6)个滴答数分配一次,idx从1开始,每次分配+1;
当T->time的第21-26位不全为0时,重新分配T[2]等级,每2^(8+6+6)个滴答数分配一次,idx从1开始,每次分配+1;
当T->time的第27-32位不全为0时,重新分配T[3]等级,每2^(8+6+6+6)个滴答数分配一次,idx从1开始,每次分配+1;
即等级越大的定时器越遥远,越不关注,需要重新分配的次数也就越少。
1 // skynet-src/skynet_timer.c2 static void3 move_list(struct timer *T, int level, int idx) {4 struct timer_node *current = link_clear(&T->t[level][idx]);5 while (current) {6 struct timer_node *temp=current->next;7 add_node(T,current);8 current=temp;9 }
10 }
11
12 static void
13 timer_shift(struct timer *T) {
14 int mask = TIME_NEAR;
15 uint32_t ct = ++T->time;
16 if (ct == 0) {
17 move_list(T, 3, 0);
18 } else {
19 uint32_t time = ct >> TIME_NEAR_SHIFT;
20 int i=0;
21
22 while ((ct & (mask-1))==0) {
23 int idx=time & TIME_LEVEL_MASK;
24 if (idx!=0) {
25 move_list(T, i, idx);
26 break;
27 }
28 mask <<= TIME_LEVEL_SHIFT;
29 time >>= TIME_LEVEL_SHIFT;
30 ++i;
31 }
32 }
33 }
3. 如何使用
在C层通过skynet_timeout创建一个定时器
在Lua层通过skynet.timeout创建一个定时器,比如,skynet.timeout(200, f),即经过200个滴答数(2秒钟)后触发回调函数f。
skynet源码分析之定时器skynet_timer.c相关推荐
- skynet源码分析 make
文章目录 skynet源码分析 make 前言 正文 platform.mk Makefile jemalloc skynet 附录 skynet源码分析 make 前言 本文的版本选择的是skyne ...
- skynet源码分析(11)--skynet的配置加载
作者:shihuaping0918@163.com,转载请注明作者 skynet中的源码已经分析得差不多了,还有启动过程没有分析.skynet的配置文件是以lua格式来写的.使用过skynet的都清楚 ...
- skynet源码分析:服务,Actor模型,lua接口编程,demo演示Actor编程思维
skynet刚开始是单进程多线程的,它是由一个一个的服务组成的.在skynet上做开发,实际上就是在写服务.服务与服务之间通过消息队列进行通信. 做为核心功能,Skynet 仅解决一个问题: 把一个符 ...
- skynet源码分析之lua层消息处理
Lua层消息处理机制在lualib/skynet.lua,提供大部分Lua层的api(最终会调用到c层的api),包括启动一个snlua服务时Lua层的处理,创建新服务,注册服务协议,如何发送消息,如 ...
- skynet源码分析5:lua绑定之地基
前面四篇已经涵盖了skynet的c层核心,剩下的timer,socket模块本身和actor模型没什么关系,且比较独立,最后再看吧.光用skynet的c接口,是很难在这上面写业务逻辑的,所以要找一种更 ...
- 【Android 异步操作】Timer 定时器 ( Timer 与 TimerTask 基本使用 | Timer 定时器常用用法 | Timer 源码分析 )
文章目录 一.Timer 定时器基本使用 二.Timer 定时器常用用法 三.Timer 源码分析 四.Timer 部分源码注释 五.源码及资源下载 参考文档 : Timer 定时器 API 文档 T ...
- skynet 框架snax源码分析----变量注入
skynet为了简化服务的编写,推出了snax框架,源码里也有一个例子pingserver.这是snax原创文章的第一篇,所以先就分析snax框架里的interface.lua源码,它的实现应用了一个 ...
- skynet源码赏析
skynet源码赏析 对于skynet,需要屡清楚的几个问题 skynet本质上解决什么问题? skynet有哪些基本的数据结构? skynet有几类线程,他们分别的作用是什么? skynet如何启动 ...
- Nginx源码分析:epoll事件处理模块概述
nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> 事件处理模块概述 Nginx的高效请求的处理依赖于事件管理机制,本次默认的场景是Linux操 ...
最新文章
- Mvc过滤器的使用【转载】
- lombok之@NoArgsConstructor、@AllArgsConstructor和@Data注解
- Sql Server 2005跨数据查询
- 数值计算(一):引论
- matlab K近邻均值平滑滤波,K近邻均值滤波
- HIVE 数据仓库容量清理
- kmplayer 修改快捷键——以倍速播放为例
- 深度学习中训练迭代次数理解【源码阅读技巧分享】【深度学习循环迭代理解】【for X, y in train_iter:】
- python撤回qq消息_仅78行代码实现微信撤回消息查看
- 原型图高保真和中保真的区别_最终确定您的高保真度UX概念验证
- 【Android学习】图片
- 安全检查监理项目部材料清单
- 使用c语言实现复数运算的程序,用C语言实现的复数运算程序设计
- 使用DBeaver连接IRIS数据库
- Python语言程序设计 第一周习题
- 李国庆是如何被“踢出”当当的?
- java输出小数_java输出保留小数点
- 微信小游戏开发入门(二)-用CocosCreator官方Demo生成微信小游戏
- 微信转账2020假图片_2020精致简约背景图片 | 简约唯美的微信朋友圈背景图
- python中文版下载安装教程,python最新版本安装教程