一、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 类也是一个抽象类,不能直接实例化。
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 类是处理描述符的,记录了与描述符相关的数据(套接字号、函数指针、传给函数的数据),也定义了两个指向自身的执行,在双向链表中可以作为一个节点(包含了数据、指向自身的指针)。同时它声明了两个友元类,可以用来访问自身的私有数据:
friend class HandlerSet; friend class HandlerIterator;
属性: socketNum 变量,套接字描述符; conditionSet 变量,条件的集合; handlerProc 变量,处理函数的 函数指针; clientData 变量,传给处理函数的数据; fNextHandler 变量,指向下一个节点的指针; fPrevHandler 变量,指向上一个节点的指针。 方法: HandlerDescriptor 函数,将此描述符添加到一个双向链表: ~HandlerDescriptor 函数,从双向链表中删除此描述符;
HandlerSet 类是处理程序(处理函数)的集合。定义了一个 HandlerDescriptor 类头节点,用双向链表来存储描述符数据,实现了描述符的添加、删除、查找等功能。同时它声明了一个友元类,可以用来访问自身的私有数据:
friend class HandlerIterator;
属性: fHandlers 变量,“处理程序”的描述符; 方法: HandlerSet 函数,初始化 fHandlers 变量,创建了一个双向链表: ~HandlerSet 函数,删除每个“处理程序”描述符; assignHandler 函数,从链表找套接字描述符,找不到就new一个并添加到链表; clearHandler 函数,从链表删除 socketNum 指定的套接字; moveHandler 函数,将旧的套接字号改为新的; lookupHandler 函数,从链表中寻找套接字描述符;
HandlerIterator 类是处理程序(处理函数)链表的迭代器,主要实现指针在链表里的偏移。
属性: fOurSet 变量,“处理程序”的集合; fNextPtr 变量,指向链表下个节点的指针; 方法: HandlerIterator 函数,初始化 fOurSet 变量,创建了一个双向链表: reset 函数,使指针重新偏移到链表的第一个节点; next 函数,使链表向下偏移一个节点,并返回该节点。
8、BasicHashTable 类
BasicHashTable 类继承自 HashTable,用于实现哈希表,在当前这个目录暂时没使用到,先不看了,等后续看到使用地方再回来补充。
总结:
UsageEnvironment 目录的源码虽然不多,但定义了好十几个类,其中比较重要的类是 BasicUsageEnvironment 类和 BasicTaskScheduler 类,其他类是在实现这两个类的过程中引申出来的,像 延时队列相关的类 和 处理函数队列相关的类 。看文章过程中可以结合代码一起看会更好理解,还有不要想着通过一篇文章就完全理解整个目录的代码了,有些类在这个目录里还没使用到,可以先知道这个类是干嘛的,等待看后面代码用到时再回来细看。这个这个目录的代码,可以知道 Live555 只有一个线程在运行,不断调用 SingleStep 函数来处理;也知道了这个目录分了几小块来实现的。