zoukankan      html  css  js  c++  java
  • 10 DelayQueue 延时队列类——Live555源码阅读(一)基本组件类

    这是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();
      }
    }
    
  • 相关阅读:
    AMQP协议
    设计模式三:行为型模式
    设计模式二:结构型模式
    设计模式一:创建型模式
    算法进阶
    数据结构
    希尔排序、计数排序、桶排序、基数排序
    归并排序
    python Gevent协程
    python——多进程
  • 原文地址:https://www.cnblogs.com/oloroso/p/4596854.html
Copyright © 2011-2022 走看看