文档章节

10 DelayQueue 延时队列类——Live555源码阅读(一)基本组件类

乌合之众
 乌合之众
发布于 2015/06/07 22:12
字数 2155
阅读 111
收藏 0

#10 DelayQueue 延时队列类——Live555源码阅读(一)基本组件类

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

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

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

10_DelayQueue

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

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

10_DelayQueue2

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。

这里的参数ETERNITYconst 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">短</font>,如果是将其延时剩余时间设置为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();
  }
}

© 著作权归作者所有

乌合之众
粉丝 13
博文 90
码字总数 79438
作品 1
海淀
程序员
私信 提问
让android支持RTSP及live555分析

#DATE 2012/05/09 #2012/08/27由cnblogs迁入 如何让Android支持C++异常机制 Android不支持C++异常机制,如果需要用到的话,则需要在编译的时候加入比较完整的C++库. Android支持的C++库可以在A...

Wii-D
2012/08/27
0
6
JAVA多线程提高十二:阻塞队列应用

一、类相关属性 接口定义: public interface BlockingQueue extends Queue { } 方法摘要 在所有方法对类中存储数据的数组做操作时,需要获取锁lock。 方法 摘要 boolean offer(E e) 将指定元...

pony1223
2018/07/16
0
0
并发编程(四)——Java中的阻塞队列

什么是阻塞队列? 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可...

whc20011
2016/10/31
9
0
【死磕Java并发】-----J.U.C之阻塞队列:BlockingQueue总结

原文出处http://cmsblogs.com/ 『chenssy』 经过前面六篇博客的阐述,我想各位应该对阻塞队列BlockingQueue有了较为深入的理解,下面来一个总结,先看整个类图: BlockingQueue BlockingQueu...

chenssy
2017/10/04
0
0
DelayQueue队列

DelayQueue是一个支持延时获取元素的无界阻塞队列。队列中的元素必须实现Delayed接口,在创建元素的时候可以指定多久才能从队列中获取当前元素,只有在延迟期满时才能从队列中获取元素。 我们...

Dreyer
2016/04/27
89
0

没有更多内容

加载失败,请刷新页面

加载更多

JAVA 8 中新增 lambda 表达式的一些基本应用

import java.util.Arrays;import java.util.List;import java.util.function.Function;import java.util.stream.Collectors;public class TestLambda { public static void......

这是一只小小鸟
23分钟前
1
0
Java向word中插入Excel文件对象

前言: 在word文件中,虽然也有表格。但是有时我们想要将Excel中表格的大量数据直接插入到word文档中,这就需要用到word的插入对象的功能,也就是直接将Excel文件当做对象插入到word中。 本地的...

qianxi
25分钟前
2
0
海量数据下的注册中心 - SOFARegistry 架构介绍

SOFAStack Scalable Open Financial Architecture Stack 是蚂蚁金服自主研发的金融级分布式架构,包含了构建金融级云原生架构所需的各个组件,是在金融场景里锤炼出来的最佳实践。 SOFARegi...

SOFAStack
28分钟前
12
0
python操作excel表格

python 对excel的操作 提示:如果需要写多个表,需要把多个表写完在保存,否则会出现表格覆盖问题 此程序 采取一边请求,一边存取,做个异常处理,即可保存已存在的数据 """# sheet的名称,...

鹏灬
30分钟前
1
0
好程序员web前端分享如何理解JS的单线程

好程序员web前端分享如何理解JS单线程,JS本质是单线程的。也就是说,它并不能像JAVA语言那样,两个线程并发执行。 但我们平时看到的JS,分明是可以同时运作很多任务的,这又是怎么回事呢? ...

好程序员IT
34分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部