这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类。

本文由乌合之众 lym瞎编,欢迎转载 www.cnblogs.com/oloroso/

本文由乌合之众 lym瞎编,欢迎转载 my.oschina.net/oloroso

DelayQueue 延时队列类

这个类的设计不是很复杂,但是要清楚的知道其设计的思路。先给个图

10_DelayQueue.png

这个链表的设计和前面不一样。其内部只有一个EventTime fLastSyncTime最后同步时间的数据成员。并不包含一个链表的头结点。但是其本身是DelayQueueEntry的派生类,所以其本身就是一个链表头结点

我们前面说了,DealyQueueEntry的构造函数是protected权限的,而DelayQueue是其友元。在后面说还会说到AlarmHandler类,这个类对象才是真正的链表节点(头结点除外)。



DelayQueue类的定义

///// DelayQueue /////
// 延时队列(链表)
class DelayQueue: public DelayQueueEntry {
public:
// 设置头结点的 延时剩余时间 为 永恒
// 设置最后同步时间为当前时间
DelayQueue();
virtual ~DelayQueue(); //添加记录(节点)
void addEntry(DelayQueueEntry* newEntry); // returns a token for the entry
void updateEntry(DelayQueueEntry* entry, DelayInterval newDelay);
void updateEntry(intptr_t tokenToFind, DelayInterval newDelay);
void removeEntry(DelayQueueEntry* entry); // but doesn't delete it
DelayQueueEntry* removeEntry(intptr_t tokenToFind); // but doesn't delete it // 获取头结点的 延时剩余时间
DelayInterval const& timeToNextAlarm();
//判断头结点的 延时剩余时间 是否为 DELAY_ZERO 是的话从链表中移除
// 并由头结点调用handleTimeout方法(delete this)
void handleAlarm();
private:
DelayQueueEntry* head() { return fNext; }
DelayQueueEntry* findEntryByToken(intptr_t token);
//把“剩余时间”域更新。
// 设置最后同步时间为当前时间
// 从链表头节点开始,遍历,看节点的延时时间是否到了,到了的设置为 DELAY_ZERO
// 从这里可以看出来,链表中节点保存的 延时剩余时间 是与前一个节点有关系的
// 当前节点 总的延时时间,应该是当前节点的 延时剩余时间 加上前一个节点的 总的延时时间
void synchronize(); // bring the 'time remaining' fields up-to-date
EventTime fLastSyncTime; //最后同步时间
};

DelayQueue的构造与析构

构造的时候,将调用了基类的构造。前面说过基类的构造就是初始化了fDeltaTimeRemaining成员(延时剩余时间),并初始化了fToken为一个不与其他节点重复的整数,同时将节点的fNextfPrev指针指向this。

这里的参数ETERNITY const DelayInterval ETERNITY(INT_MAX, MILLION-1); //最大的时间(永恒) 的定义,其可能在不同平台有不同值,但肯定是一个非常大的数。也就是说头结点的延时剩余时间是一个特殊的值,正常情况下不会有比它更大的了。

这里还设置了最后同步时间为当前时间

DelayQueue::DelayQueue()
: DelayQueueEntry(ETERNITY) {
fLastSyncTime = TimeNow();
}

析构函数做的时间就比较多了,它负责了释放链表的操作。

DelayQueue::~DelayQueue() {
while (fNext != this) {
DelayQueueEntry* entryToRemove = fNext;
removeEntry(entryToRemove);
delete entryToRemove;
}
}

removeEntry方法

这个方法在析构函数中用到了,就是把节点从链表中移除。要注意的是,其只是把节点移出了链表,并没有销毁哦。

这里注意看这一句entry->fNext->fDeltaTimeRemaining += entry->fDeltaTimeRemaining;

移除节点的下一个节点的延时间隔剩余时间增加了移除节点的延时剩余时间。这里说明了这个队列的节点的延时间隔剩余时间不是其成员fDeltaTimeRemaining所表示的值,而是其与其之前所有节点的fDeltaTimeRemaining之和才是真的延时剩余时间。这一点很重要,后面的其他方法中要知道这个设计才行。

void DelayQueue::removeEntry(DelayQueueEntry* entry) {
if (entry == NULL || entry->fNext == NULL) return; entry->fNext->fDeltaTimeRemaining += entry->fDeltaTimeRemaining;
entry->fPrev->fNext = entry->fNext;
entry->fNext->fPrev = entry->fPrev;
entry->fNext = entry->fPrev = NULL;
// in case we should try to remove it again
}

其还有一个重载DelayQueueEntry* DelayQueue::removeEntry(intptr_t tokenToFind)相当于是先查找,再移除。

findEntryByToken方法

这个方法用于查找节点,找到了返回节点的地址,没找到返回NULL

DelayQueueEntry* DelayQueue::findEntryByToken(intptr_t tokenToFind) {
DelayQueueEntry* cur = head();
while (cur != this) {
if (cur->token() == tokenToFind) return cur;
cur = cur->fNext;
}
return NULL;
}

synchronize方法

这是DelayQueue中非常重要的一个方法,并且这个方法是private权限的,只能在类内部调用。

1.先获取当前时间,然后比较当前时间与最后一次同步的时间。如果当前时间在最后一次同步时间之后,做下面的步骤

2.计算出自上次同步之后,又经过了多长时间。时间差为timeSinceLastSync,设置最后同步时间为当前时间。

3.从头结点的下一个开始,判断其 延时剩余时间 是否比 已经过去的时间 <fontcolor="#FF0000">短,如果是将其延时剩余时间设置为0 。并将timeSinceLastSync减去 这个节点的延时剩余时间,因为实际的延时剩余实际是与前一个节点相关的

4.当找到 延时剩余时间timeSinceLastSync长的节点的时候,说明当前节点的延时还得继续,操作到此,将其延时剩余时间减去timeSinceLastSync。同步至此完成


可以看出这个方法的作用就是判断节点的延时是否到了,进行的一次更新。

void DelayQueue::synchronize() {
// First, figure out how much time has elapsed since the last sync:
// 首先,计算出自上次同步时间后又过了多少时间:
EventTime timeNow = TimeNow();
if (timeNow < fLastSyncTime) {
// The system clock has apparently gone back in time; reset our sync time and return:
//系统时钟显然已经回到了过去;重置我们的最后同步时间并返回:
fLastSyncTime = timeNow;
return;
}
DelayInterval timeSinceLastSync = timeNow - fLastSyncTime;
fLastSyncTime = timeNow; // Then, adjust the delay queue for any entries whose time is up:
// 然后,调整延迟队列中的任何项的时间到了:(从链表头节点开始,遍历,看节点的延时时间是否到了)
DelayQueueEntry* curEntry = head();
while (timeSinceLastSync >= curEntry->fDeltaTimeRemaining) {
timeSinceLastSync -= curEntry->fDeltaTimeRemaining;
curEntry->fDeltaTimeRemaining = DELAY_ZERO;
curEntry = curEntry->fNext;
}
curEntry->fDeltaTimeRemaining -= timeSinceLastSync;
}

addEntry方法

addEntry是添加节点的方法,这个节点必须是已经存在的。我们之前说明,节点的创建是由AlarmHandler来完成的。为什么这么肯定呢?因为DelayQueue类中没有任何方法创建了DelayQueueEntry对象。这里有一个问题就是,如果参数newEntry为NULL呢?

这里先是更新了一下同步剩余时间,然后在链表中找到合适的位置,插入节点。查找的时候实际上也更新了延时剩余时间。

void DelayQueue::addEntry(DelayQueueEntry* newEntry) {
synchronize();
//这里应该判断一下 newEntry == NULL的情况
DelayQueueEntry* cur = head();
while (newEntry->fDeltaTimeRemaining >= cur->fDeltaTimeRemaining) {
newEntry->fDeltaTimeRemaining -= cur->fDeltaTimeRemaining;
cur = cur->fNext;
} cur->fDeltaTimeRemaining -= newEntry->fDeltaTimeRemaining; // Add "newEntry" to the queue, just before "cur":
newEntry->fNext = cur;
newEntry->fPrev = cur->fPrev;
cur->fPrev = newEntry->fPrev->fNext = newEntry;
}

updateEntry方法

updateEntry实现将节点的延时剩余时间更新。先找出节点,然后从链表移出更新延时剩余时间,再把它添加到链表

void DelayQueue::updateEntry(DelayQueueEntry* entry, DelayInterval newDelay) {
if (entry == NULL) return; removeEntry(entry);
entry->fDeltaTimeRemaining = newDelay;
addEntry(entry);
}

其还有重载形式void DelayQueue::updateEntry(intptr_t tokenToFind, DelayInterval newDelay)

timeToNextAlarm方法

timeToNextAlarm方法返回第一个节点的延时剩余时间。注意这里说的第一个节点不是头结点哦。

这里判断一下第一个节点延时剩余时间是否为0很有必要,如果不为0要更新一次。因为当前时间可能不是最后一次同步时间。如果为0,可以不用更新,提升效率。

DelayInterval const& DelayQueue::timeToNextAlarm() {
if (head()->fDeltaTimeRemaining == DELAY_ZERO) return DELAY_ZERO; // a common case synchronize();
return head()->fDeltaTimeRemaining;
}

handleAlarm方法

这个方法很重要,为什么呢?我们知道每一个节点都是一个AlarmHandler对象,这个对象的handleTimeout方法做了一件事情,就是使用了一个函数指针调用了一个函数,想一想前面的HandlerDescriptor类,是不是处理任务了呢!

本来应先说AlarmHandler类的,因为它们不在一个文件中,所以放在后面说。

handleAlarm方法中将延时等待时间已经到了的(也就是延时剩余时间已经为0的)对象从链表中移出,并调用其handleTimeout方法去处理任务。

void DelayQueue::handleAlarm() {
if (head()->fDeltaTimeRemaining != DELAY_ZERO) synchronize(); if (head()->fDeltaTimeRemaining == DELAY_ZERO) {
// This event is due to be handled:
// 这事件是由于要处理:
DelayQueueEntry* toRemove = head();
removeEntry(toRemove); // do this first, in case handler accesses queue toRemove->handleTimeout();
}
}

10 DelayQueue 延时队列类——Live555源码阅读(一)基本组件类的更多相关文章

  1. 12 哈希表相关类——Live555源码阅读(一)基本组件类

    12 哈希表相关类--Live555源码阅读(一)基本组件类 这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. 本文由乌合之众 lym瞎编,欢迎转载 ...

  2. 9 DelayQueueEntry 延时队列节点类——Live555源码阅读(一)基本组件类

    这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. 本文由乌合之众 lym瞎编,欢迎转载 http://www.cnblogs.com/oloroso ...

  3. 8 延时队列相关类——Live555源码阅读(一)基本组件类

    这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. 本文由乌合之众 lym瞎编,欢迎转载 http://www.cnblogs.com/oloroso ...

  4. 2 DelayInterval延时间隔类——Live555源码阅读(一)基本组件类

    这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. 这里是时间相关类的第二个部分. 本文由乌合之众 lym瞎编,欢迎转载 http://www.cnb ...

  5. 4 Handler相关类——Live555源码阅读(一)基本组件类

    这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. Handler相关类概述 处理程序相关类一共有三个,其没有派生继承关系,但是其有友元关系和使用关系 ...

  6. TimeVal类——Live555源码阅读(一)基本组件类

    这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. 这里是时间相关类的第一个部分. TimeVal类 TimeVal类定义在live555source ...

  7. 13 HashTable抽象哈希表类——Live555源码阅读(一)基本组件类

    这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. 本文由乌合之众 lym瞎编,欢迎转载 http://www.cnblogs.com/oloroso ...

  8. 11 AlarmHandler定时处理类——Live555源码阅读(一)基本组件类

    这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. 本文由乌合之众 lym瞎编,欢迎转载 http://www.cnblogs.com/oloroso ...

  9. 7 HandlerSet 处理程序链表类——Live555源码阅读(一)基本组件类

    这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. 本文由乌合之众 lym瞎编,欢迎转载 my.oschina.net/oloroso Handler ...

随机推荐

  1. JavaWeb学习笔记——表达式语言

    使用表达式语言,可以方便地访问标志位(JSP中有page(pageContext).request.session和application4种标志位)中的属性内容,可以避免出现许多的Scriptlet ...

  2. Java数据结构——用双端链表实现队列

    //================================================= // File Name : LinkQueue_demo //---------------- ...

  3. DllMaps

    http://www.mono-project.com/docs/advanced/pinvoke/dllmap/ http://www.mono-project.com/docs/advanced/ ...

  4. Semantic ui 学习笔记 持续更新

    这个semantic 更新版本好快~ 首先是代码的标识<code></code> 具体样式就是红框这样的 圈起来代码感觉不错 不过要在semantic.css里在加上如下样式~ ...

  5. CentOS 6.5安装配置LNMP服务器(Nginx+PHP+MySQL)

    CentOS 6.5安装配置LNMP服务器(Nginx+PHP+MySQL) 一.准备篇: /etc/init.d/iptables stop #关闭防火墙 关闭SELINUX vi /etc/sel ...

  6. 工具,如何去掉百度编辑器 ueditor 元素路径、字数统计等

    去掉如下截图: 在百度编辑器 ueditor 根目录下: ueditor.config.js 文件中 搜索并将参数elementPathEnabled设置成false即可 常用功能开关如下: ,ele ...

  7. 使用idea开发Springmvc

    使用IntelliJ IDEA开发SpringMVC网站(一)开发环境 http://my.oschina.net/gaussik/blog/385697?fromerr=A4EKE0Ix idea1 ...

  8. 合并两个结构完全相同的DataTable

    两个结构一模一样的DataTable如何合并? 例子:使用Winform进行演示,表2的数据为固定的,表1的数据可以动态添加,通过合并按钮合并表1和表2的数据到表3 1.规定公共的DataTable结 ...

  9. linux shell 报错 Syntax error: Bad for loop variable

    在linux下写了一个简单的shell,循环10次. test.sh #!/bin/bash ## ##循环10次 ## ; i<; i++)); do echo Good Morning ,t ...

  10. 梳理javascript原型整体思路

    相信很多对javascript原型初步了解的人都知道prototype,constructor,__proto__这些名词,也在一定程度上可以使用这些对象.属性.甚至知道在构造函数的原型上定义方法供实 ...