Live555源码阅读笔记(一):源码介绍文档 及 源码目录结构
Live555源码阅读笔记(二):UsageEnvironment 目录详解
Live555源码阅读笔记(三):BasicUsageEnvironment 目录详解
目录
5、Timeval类、DelayInterval类、_EventTime类
6、DelayQueueEntry类、DelayQueue类、AlarmHandler类
7、HandlerDescriptor类、HandlerSet类、HandlerIterator类
一、BasicUsageEnvironment 目录介绍
BasicUsageEnvironment 目录总共有12个源码文件,定义了“UsageEnvironment”类的一个具体实现(即子类),用于输出错误/警告信息;也定义了"TaskScheduler"的具体实现(子类),用于任务调度;还有哈希表的具体实现。
注意:“UsageEnvironment”类和"TaskScheduler"类是定义在 UsageEnvironment 目录的,需要了解更仔细的可以看上一篇文章(UsageEnvironment 目录详解)。
二、阅读代码
这个目录代码也简单,实现了 UsageEnvironment 类的两个子类 BasicUsageEnvironment0 类和BasicUsageEnvironment 类;实现了 TaskScheduler 类的两个子类 BasicTaskScheduler0 类和 BasicTaskScheduler 类;实现了 HashTable 类的子类 BasicHashTable;还有其他的关于延时队列的和时间、描述符队列的类,下面逐个看一下,目的是看完之后对代码结构以及这几个类有个基本的认识,再结合代码,更好地理解。
BasicUsageEnvironment 、BasicTaskScheduler、BasicHashTable的继承图:
1、BasicUsageEnvironment0 类
由继承顺序,我们先看 BasicUsageEnvironment0 类的源码,BasicUsageEnvironment0 类的父类是 UsageEnvironment 类,由于 BasicUsageEnvironment0 类没有实现父类的所有纯虚函数,所以BasicUsageEnvironment0 类也是一个抽象类,不能直接实例化。
BasicUsageEnvironment0 类主要是重写了 UsageEnvironment 类中与“错误/警告信息字符串” 相关的函数,函数实现也比较简单,没有需要特殊说明的。
virtual MsgString getResultMsg() const;
virtual void setResultMsg(MsgString msg);
virtual void setResultMsg(MsgString msg1,MsgString msg2);
virtual void setResultMsg(MsgString msg1,MsgString msg2,MsgString msg3);
virtual void setResultErrMsg(MsgString msg, int err = 0);
virtual void appendToResultMsg(MsgString msg);
virtual void reportBackgroundError();
2、BasicUsageEnvironment 类
BasicUsageEnvironment 类的父类是 BasicUsageEnvironment0 类,它实现了 BasicUsageEnvironment0 类没实现的其他纯虚函数,并且没有声明新的纯虚函数,所以这个类不是抽象类,由于其构造函数 BasicUsageEnvironment 是 protected 属性的,所以这个类不能直接实例化,只能通过类内部定义的 createNew 函数来获取到指向 BasicUsageEnvironment 的指针,再通过指针使用该类。
// new 一个BasicUsageEnvironment对象,并将指针作为返回值
static BasicUsageEnvironment* createNew(TaskScheduler& taskScheduler);
下面是重写 UsageEnvironment 类跟 “重载<<运算符” 相关的函数,它的实现是直接将输入参数通过fprintf 函数打印到 stderr(标准错误文件描述符),直接看代码即可。
UsageEnvironment& BasicUsageEnvironment::operator<<(char const* str) {
if (str == NULL) str = "(NULL)"; // sanity check
fprintf(stderr, "%s", str); return *this;
}
UsageEnvironment& BasicUsageEnvironment::operator<<(int i) {
fprintf(stderr, "%d", i); return *this;
}
UsageEnvironment& BasicUsageEnvironment::operator<<(unsigned u) {
fprintf(stderr, "%u", u); return *this;
}
UsageEnvironment& BasicUsageEnvironment::operator<<(double d) {
fprintf(stderr, "%f", d); return *this;
}
UsageEnvironment& BasicUsageEnvironment::operator<<(void* p) {
fprintf(stderr, "%p", p); return *this;
}
3、BasicTaskScheduler0 类
BasicTaskScheduler0 类的父类是 TaskScheduler 类,由于 BasicTaskScheduler0 类仍存在纯虚函数,所以它也是一个抽象类,不能直接实例化。
3.1、BasicTaskScheduler0 类的属性:
fDelayQueue 变量,延时队列,用于实现延时操作;
fHandlers 变量,处理函数(处理程序)的集合,用于实现后台读取;
fLastHandledSocketNum 变量,上次处理的描述符;
fTriggersAwaitingHandling 变量,按位操作,记录了“等待处理”的触发器id;
fLastUsedTriggerMask 变量,按位操作,记录了上次使用的触发器id;
fTriggeredEventHandlers 变量,与触发器id对应的 函数指针数组;
fTriggeredEventClientDatas 变量,传给函数的组成的数组;
fLastUsedTriggerNum 变量,记录已使用的触发器数量。
3.2、BasicTaskScheduler0 类的方法:
类的开头声明了一个纯虚函数:
virtual void SingleStep(unsigned maxDelayTime = 0) = 0;
接着,重写了父类 TaskScheduler 关于 任务调度 和 事件触发 相关的函数,实现过程使用到DelayInterval、AlarmHandler、DelayQueueEntry等类,这些类在后面几个小节有介绍:
//做一些初始化变量的工作,new 了一个 HandlerSet
BasicTaskScheduler0();
// new 一个定时处理对象(AlarmHandler),并添加到延时队列
virtual TaskToken scheduleDelayedTask(int64_t microseconds,TaskFunc* proc,void* clientData);
// 根据令牌从延时队列里删除一个任务
virtual void unscheduleDelayedTask(TaskToken& prevTask);
// 一直循环调用 SingleStep 处理
virtual void doEventLoop(char volatile* watchVariable);
// 把 eventHandlerProc 注册成一个事件触发器,并返回触发器id,最多可以创建 32 个
virtual EventTriggerId createEventTrigger(TaskFunc* eventHandlerProc);
// 根据事件触发器id,删除事件触发器
virtual void deleteEventTrigger(EventTriggerId eventTriggerId);
// 触发指定 id 的事件
virtual void triggerEvent(EventTriggerId eventTriggerId, void* clientData = NULL);
4、BasicTaskScheduler 类
BasicTaskScheduler 类的父类是 BasicTaskScheduler0 类,它重写了 BasicTaskScheduler0 类和 TaskScheduler 类的所有纯虚函数,但其构造函数 BasicTaskScheduler 是 protected 属性的,所以这个类不能直接实例化,只能通过类内部定义的 createNew 函数来获取到指向 BasicTaskScheduler 的指针,再通过指针使用该类。
4.1、BasicTaskScheduler 类的属性:
fMaxSchedulerGranularity 变量,最大调度器粒度,表示在事件循环返回前,在select中等待的最长时间,默认是 10ms;
fMaxNumSockets变量,最大的套接字描述符,用于 select 函数
fReadSet 变量,描述符集-读取;
fWriteSet 变量,描述符集-写入;
fExceptionSet 变量,描述符集-异常。
4.2、BasicTaskScheduler 类的方法:
public:
// new 一个 BasicTaskScheduler并返回该对象指针
static BasicTaskScheduler* createNew(unsigned maxSchedulerGranularity = 10000/*microseconds*/);
protected:
//构造函数,初始化并调用一次 schedulerTickTask。只在 createNew 函数会被调用
BasicTaskScheduler(unsigned maxSchedulerGranularity);
// 将 clientData 转为 BasicTaskScheduler,再调用 schedulerTickTask()
static void schedulerTickTask(void* clientData);
// 调用 scheduleDelayedTask 创建一个延时任务;
// scheduleDelayedTask的第二个参数是 TaskFunc 类型的,
// 所以第二个参数传的应该是“static void schedulerTickTask(void* clientData)”
void schedulerTickTask();
// 1.调用 select 等待一个时间 tv_timeToDelay
// 2.为一个可读套接字调用处理函数(从上次处理的套接字开始,在描述符队列往后找,找不到再从头找)
// 3.处理任何新触发的事件
virtual void SingleStep(unsigned maxDelayTime);
// 将一个处理函数添加到后台处理(添加到处理函数队列)
virtual void setBackgroundHandling(int socketNum, int conditionSet, BackgroundHandlerProc* handlerProc, void* clientData);
// 更改处理函数的描述符
virtual void moveSocketHandling(int oldSocketNum, int newSocketNum);
5、Timeval类、DelayInterval类、_EventTime类
这几个类都是和时间相关的且比较简单,放在一起说明。
Timeval 类可以是绝对时间,也可以是时间间隔,是对系统定义的结构体 struct timeval(定义在<sys/time.h>) 的一个扩展。Timeval 类主要实现了8个运算符重载函数(>=, <=, ==, !=, >, <, +=, -=),方便使用。其构造函数是 protected 的,故不能直接实例化对象。
DelayInterval 类继承自 Timeval 类,延时间隔,对 Timeval 类进一步封装。
_EventTime 类继承自 Timeval 类,事件的绝对时间。
6、DelayQueueEntry类、DelayQueue类、AlarmHandler类
DelayQueueEntry 是一个抽象基类,延时队列入口,其作用是实现延时队列(链表)的一个节点,包含了指向自身的两个指针和数据(fDeltaTimeRemaining)。声明了一个友元类,可以用来访问自身的私有数据。
friend class DelayQueue;
属性:
静态变量 tokenCounter,用来计算令牌总个数
fToken 变量,实例化时返回 tokenCounter 的值;
fDeltaTimeRemaining 变量,保存剩余时间;
fNext 和 fPrev 变量,作为双向队列的节点指针;
方法:
handleTimeout 函数,用来处理超时的情况,超时就 delete 当前对象指针;
DelayQueueEntry 函数,初始化剩余时间,令牌总个数加一。
DelayQueue 继承自 DelayQueueEntry 类,延时队列,是对DelayQueueEntry 的扩展,实现了节点的添加、更新、删除。因为 DelayQueueEntry 声明了DelayQueue 为其友元类,所以DelayQueue 类可以访问到 DelayQueueEntry 的私有数据。
属性:
fLastSyncTime 变量,记录上次同步时间;
方法:
DelayQueue 函数,初始化队列,更新同步时间为现在;
~DelayQueue 函数,清空队列,从队列里删除每一个DelayQueueEntry;
addEntry 函数,同步并添加一个 entry 到队列里;
updateEntry 函数,更新队列里的 entry 的剩余时间。
removeEntry 函数,从队列里删除一个 entry ,但没有删除该对象的指针。
timeToNextAlarm 函数,获取下一个 entry 的剩余时间。
handleAlarm 函数,处理剩余时间,把剩余时间为0的从队列里彻底删除。
findEntryByToken 函数,从队列里查找指定的 entry。
synchronize 函数,计算上次同步到现在的时间,更新"到时间"的节点
AlarmHandler 是 DelayQueueEntry 的子类,用于实现定时处理。
属性:
fProc 变量,函数指针,指向 “到达时间时要调用” 的函数。
fClientData 变量,传给定时函数的数据。
方法:
handleTimeout 函数,调用定时处理函数,然后 delete 对象指针。
7、HandlerDescriptor类、HandlerSet类、HandlerIterator类
这三个类实现了一个双向链表的必要内容:
- HandlerDescriptor:定义了链表结点;
- HandlerSet:定义了双向链表,实现增删改查;
- HandlerIterator:链表迭代器,实例化时需要传入链表引用;
7.1 HandlerDescriptor 类
HandlerDescriptor 类是处理描述符的,记录了与描述符相关的数据(套接字号、函数指针、传给函数的数据),也定义了两个指向自身的执行,在双向链表中可以作为一个节点(包含了数据、指向自身的指针)。同时它声明了两个友元类,可以用来访问自身的私有数据:
friend class HandlerSet;
friend class HandlerIterator;
属性:
socketNum 变量,套接字描述符;
conditionSet 变量,条件的集合;
handlerProc 变量,处理函数的 函数指针;
clientData 变量,传给处理函数的数据;
fNextHandler 变量,指向下一个节点的指针;
fPrevHandler 变量,指向上一个节点的指针。
方法:
HandlerDescriptor 函数,将此描述符添加到一个双向链表:
~HandlerDescriptor 函数,从双向链表中删除此描述符;
7.2 HandlerSet 类
HandlerSet 类是处理程序(处理函数)的集合。定义了一个 HandlerDescriptor 类头节点,用双向链表来存储描述符数据,实现了描述符的添加、删除、查找等功能。同时它声明了一个友元类,可以用来访问自身的私有数据:
friend class HandlerIterator;
属性:
fHandlers 变量,“处理程序”的描述符;
方法:
HandlerSet 函数,初始化 fHandlers 变量,创建了一个双向链表:
~HandlerSet 函数,删除每个“处理程序”描述符;
assignHandler 函数,从链表找套接字描述符,找不到就new一个并添加到链表;
clearHandler 函数,从链表删除 socketNum 指定的套接字;
moveHandler 函数,将旧的套接字号改为新的;
lookupHandler 函数,从链表中寻找套接字描述符;
7.3 HandlerIterator 类
HandlerIterator 类是处理程序(处理函数)链表的迭代器,主要实现指针在链表里的偏移。
属性:
fOurSet 变量,“处理程序”的集合;
fNextPtr 变量,指向链表下个节点的指针;
方法:
HandlerIterator 函数,初始化 fOurSet 变量,创建了一个双向链表:
reset 函数,使指针重新偏移到链表的第一个节点;
next 函数,使链表向下偏移一个节点,并返回该节点。
8、BasicHashTable 类
BasicHashTable 类继承自 HashTable,用于实现哈希表,在当前这个目录暂时没使用到,先不看了,等后续看到使用地方再回来补充。
总结:
UsageEnvironment 目录的源码虽然不多,但定义了好十几个类,其中比较重要的类是 BasicUsageEnvironment 类和 BasicTaskScheduler 类,其他类是在实现这两个类的过程中引申出来的,像 延时队列相关的类 和 处理函数队列相关的类 。看文章过程中可以结合代码一起看会更好理解,还有不要想着通过一篇文章就完全理解整个目录的代码了,有些类在这个目录里还没使用到,可以先知道这个类是干嘛的,等待看后面代码用到时再回来细看。这个这个目录的代码,可以知道 Live555 只有一个线程在运行,不断调用 SingleStep 函数来处理;也知道了这个目录分了几小块来实现的。