这是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. 结合Hadoop,简单理解SSH

    在启动dfs和yarn时,需要多次输入密码,不但启动本机进程还有辅服务器启动那些节点也需要相应密码,主与辅服务器之间是通过SSH连接的,并发送操作指令 一.ssh密码远程登录 1.使用ssh连接另一台 ...

  2. JavaScript----遇到的问题

    <script type="text/javascript" src="a.js"> alert('dd'); </script> 存在 ...

  3. 一段发工资的shell代码

    人事发工资条之前是一个个截图发到我们的邮箱里,看人事妹纸是一个善良而又美丽的姑凉,于是乎写了一段shell代码实现批量发短信至各个手机号.不多说了,上代码,其实很简单,我都不好意思上传,还是记录下吧, ...

  4. day4之函数

    很快就第4天了,原来人是有惰性的,博客现在就不想写了,真是悲催,坚持,憋住. 函数 def func(name): print(name) func("huihuang") 函数定 ...

  5. Ajax与DOM实现动态加载

    阅读目录 DOM如何动态添加节点 Ajax异步请求 Chrome处理本地Ajax异步请求 参考: 首先说下问题背景:想要通过异步请求一个文本文件,然后通过该文件的内容动态创建一个DOM节点添加到网页中 ...

  6. ecshop 订单-》订单状态

    /** * 取得状态列表 * @param string $type 类型:all | order | shipping | payment */ function get_status_list($ ...

  7. js blind使用

    $("#music_up").bind("click",showData()); $("#music_up").bind("cli ...

  8. python 跨语言数据交互、json、pickle(序列化)、urllib、requests(爬虫模块)、XML。

    Python中用于序列化的两个模块 json     用于[字符串]和 [python基本数据类型] 间进行转换 pickle   用于[python特有的类型] 和 [python基本数据类型]间进 ...

  9. Ubuntu14.04 LTS更新源

    Ubuntu14.04 LTS更新源 不同的网络状况连接以下源的速度不同, 建议在添加前手动验证以下源的连接速度(ping下就行),选择最快的源可以节省大批下载时间. 首先备份源列表: sudo cp ...

  10. c#之Redis队列在邮件提醒中的应用

    场景 有这样一个场景,一个邮件提醒的windows服务,获取所有开启邮件提醒的用户,循环获取这些用户的邮件,发送一条服务号消息.但问题来了,用户比较少的情况下,轮询一遍时间还能忍受,如果用户多了,那用 ...