TaskSecheduler类是一个任务调度器,它是整个Live555的任务调度中心,程序在任何时刻的任何动作,均由该类完成。其核心函数是SingleStep。Live555的任务主要分为Delayed Event、Socket Event以及Trigger Event。其类层次结构如下图所示:
从类图中可以看到,其类层次结构和第二节中的UsageEnvironment类极其相似。下面让我们对每个类进行一个简单的认识。
TaskSecheduler定义了一些接口,用来对各种任务进行管理。该类中最为重要的一个函数莫过于doEventLoop了,整个任务的调度便是通过调用该函数实现的。每一次循环,该函数都会执行如下的几步:
1. 首先处理IO事件。程序通过select函数选择那些已经准备好的IO文件描述符,对其进行读、写或者异常处理。与该任务相关的接口为setBackgroundHandling、disableBackgroundHandling以及moveSocketHandling。IO事件会在任务中反复执行。
2. 然后将处理触发事件。由于作者采用了一个机器字节中的位来保存触发事件的数量,所以触发事件的数量受机器限制。对X86系统则32个触发事件。这样做的好处是效率高,但缺点就是数量受限制。与该任务相关的接口为createEventTrigger、deleteEventTrigger和triggerEvent。事件一旦被触发后,将会立即删除,避免事件再次触发。
3. 最后将执行延迟任务。延迟任务保存在一个延迟队列中,通过时间来指定何时执行。有关延迟任务,我们放在后面的小节中来专门学习。延迟任务执行后也将从延迟队列事删除。
BasicTaskScheduler0实现了延迟任务和触发事件。触发事件是通过一个机器字长所能表示的位来处理的,在内部被定义为fTriggersAwaitingHandling,是int类型。从最高位开始保存。比如,假设系统是32位机,并且只有一个待触发事件,那么fTriggersAwaitingHandling的值为0x80000000(对应二进制为10000000 0000 0000 0000 0000 0000 0000)。该类中还保存了上一次触发事件的ID以及mask,作为下一次调度的起点。这样保证了调度程序能够有序的执行所有待触发事件,而不是每次都从头开始扫描。它们的关系如下图所示:
至于延迟任务的实现,我们放在单独的小节中来学习。
BasicTaskScheduler实现了最后一个任务,即IO事件和核心调度程序SingleStep。IO任务的实现也很简单,它被定义为一个双向循环链表,链表的节点为HandlerDiscriptor。其实现类为HandlerSet,该类实现了对链表的增删改查操作。与之对应的,作者还定义了一个迭代器类HandlerIterator,用于遍历链表。而调度程序则实现上面所提到的三步操作,来依次执行各类任务。现在给出该函数的实现(为了帮助你的理解,我添加了一些中文注释):
void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {fd_set readSet = fReadSet;fd_set writeSet = fWriteSet;fd_set exceptionSet = fExceptionSet;DelayInterval const& timeToDelay = fDelayQueue.timeToNextAlarm();struct timeval tv_timeToDelay;tv_timeToDelay.tv_sec = timeToDelay.seconds();tv_timeToDelay.tv_usec = timeToDelay.useconds();/*** 一个太大的tv_sec将导致select函数失败* 因此,确保它不大于一万秒,即11.5天*/const long MAX_TV_SEC = MILLION;if (tv_timeToDelay.tv_sec > MAX_TV_SEC) {tv_timeToDelay.tv_sec = MAX_TV_SEC;}/*** 检查最大延迟时间是否大于一万秒* 以及微秒数是否大于一百万微秒,即1秒*/if (maxDelayTime > 0 && (tv_timeToDelay.tv_sec > (long) maxDelayTime / MILLION|| (tv_timeToDelay.tv_sec == (long) maxDelayTime / MILLION&& tv_timeToDelay.tv_usec > (long) maxDelayTime % MILLION))) {tv_timeToDelay.tv_sec = maxDelayTime / MILLION;tv_timeToDelay.tv_usec = maxDelayTime % MILLION;}int selectResult = select(fMaxNumSockets, &readSet, &writeSet,&exceptionSet, &tv_timeToDelay);if (selectResult < 0) {if (errno != EINTR && errno != EAGAIN) {/*** 哭了,错误*/internalError();}}/*** 迭代器*/HandlerIterator iter(*fHandlers);HandlerDescriptor* handler;/*** 找到上次执行后的处理程序队列中的下一个* 这里先找到上次执行时的socket号*/if (fLastHandledSocketNum >= 0) {while ((handler = iter.next()) != NULL) {if (handler->socketNum == fLastHandledSocketNum)break;}/*** 没有找到,可能已经被移除,重置延时队列*/if (handler == NULL) {fLastHandledSocketNum = -1;iter.reset();}}/*** 从找到的handler开始,执行其下一个,不管其状态是什么,皆执行* 当然,也可能是从队列头开始执行的*/while ((handler = iter.next()) != NULL) {int sock = handler->socketNum;int resultConditionSet = 0;/*** 检查*/if (FD_ISSET(sock, &readSet) && FD_ISSET(sock, &fReadSet))resultConditionSet |= SOCKET_READABLE;if (FD_ISSET(sock, &writeSet) && FD_ISSET(sock, &fWriteSet))resultConditionSet |= SOCKET_WRITABLE;if (FD_ISSET(sock, &exceptionSet) && FD_ISSET(sock, &fExceptionSet))resultConditionSet |= SOCKET_EXCEPTION;if ((resultConditionSet & handler->conditionSet) != 0&& handler->handlerProc != NULL) {/*** 保存sock号,调度程序下次将从该位置继续执行,下同*/fLastHandledSocketNum = sock;/*** 调用事件处理函数*/(*handler->handlerProc)(handler->clientData, resultConditionSet);break;}}/*** 我们没有调用处理程序* 因此,从重再来一次* 造成这样的原因可能是从上一次执行处理程序的位置向后没有找到任何可执行的处理程序了* 于是从头开始寻找处理程序*/if (handler == NULL && fLastHandledSocketNum >= 0) {iter.reset();while ((handler = iter.next()) != NULL) {int sock = handler->socketNum;int resultConditionSet = 0;if (FD_ISSET(sock, &readSet) && FD_ISSET(sock, &fReadSet))resultConditionSet |= SOCKET_READABLE;if (FD_ISSET(sock, &writeSet) && FD_ISSET(sock, &fWriteSet))resultConditionSet |= SOCKET_WRITABLE;if (FD_ISSET(sock, &exceptionSet) && FD_ISSET(sock, &fExceptionSet))resultConditionSet |= SOCKET_EXCEPTION;if ((resultConditionSet & handler->conditionSet) != 0&& handler->handlerProc != NULL) {fLastHandledSocketNum = sock;(*handler->handlerProc)(handler->clientData, resultConditionSet);break;}}/*** 依然没有找到任务何执行的handler* 将其值置为-1* 以告诉处理程序,下次应该从头开始寻找handler*/if (handler == NULL)fLastHandledSocketNum = -1;}/*** 响应新触发的事件*/if (fTriggersAwaitingHandling != 0) {/*** 首先检查是否只有一个待触发事件*/if (fTriggersAwaitingHandling == fLastUsedTriggerMask) {fTriggersAwaitingHandling = 0;if (fTriggeredEventHandlers[fLastUsedTriggerNum] != NULL) {/*** 执行事件处理函数*/(*fTriggeredEventHandlers[fLastUsedTriggerNum])(fTriggeredEventClientDatas[fLastUsedTriggerNum]);}} else {/*** 寻找待执行的触发事件*/unsigned i = fLastUsedTriggerNum;EventTriggerId mask = fLastUsedTriggerMask;do {i = (i + 1) % MAX_NUM_EVENT_TRIGGERS;mask >>= 1;if (mask == 0)mask = 0x80000000;if ((fTriggersAwaitingHandling & mask) != 0) {fTriggersAwaitingHandling &= ~mask;if (fTriggeredEventHandlers[i] != NULL) {/*** 响应事件*/(*fTriggeredEventHandlers[i])(fTriggeredEventClientDatas[i]);}fLastUsedTriggerMask = mask;fLastUsedTriggerNum = i;break;}} while (i != fLastUsedTriggerNum);}}/*** 执行一个最迫切的延迟任务*/fDelayQueue.handleAlarm();
}
好了,到目前为止,我们已经了解了这三个类的作用及其工作原理,下面让我们来写一段测试代码,分别对三类任务进行测试。下面给出我的实现:
/** TestTaskScheduler.cpp** Created on: 2012-4-10* Author: lovey599** 通过以下测试程序,我们可以看到:* 除了IO任务以外的其它任务* 执行后将从队列中删除* 不再执行* 通过以下测试代码,你是否对doEventLoop函数有了更加清晰的认识?* 对整个框架的启动机制有了清晰的了解?* 同时,我们也可以看到,调度中心就是一个永真循环,并未对多线程提供更多的支持*/#include <iostream>#include "BasicUsageEnvironment.hh"using namespace std;/*** 后台IO任务处理函数(包括socket)* 此处将数据输出到控制台*/
void taskFun(void* clientData, int mask) {/*** 检查*/do {if (mask & SOCKET_EXCEPTION) {cout << "IO Event:Oh,my god!" << endl;break;}if (mask & SOCKET_WRITABLE) {cout << "IO Event(Writable):" << (char*) clientData << endl;}if (mask & SOCKET_READABLE) {cout << "IO Event(Readable):" << (char*) clientData << endl;}sleep(1);} while (0);
}/*** 触发事件回调函数*/
void eventFun(void* clientData) {cout << "Event Trigger:" << (char*) clientData << endl;sleep(1);
}/*** 延迟任务回调函数*/
void delayedFun(void* clientData) {cout << "Delayed Task:" << (char*) clientData << endl;sleep(1);
}int main(int argc, char* argv[]) {TaskScheduler* scheduler = BasicTaskScheduler::createNew();UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);char taskFunData[] = "do task...";/*** 触发事件测试*/EventTriggerId id = scheduler->createEventTrigger(&eventFun);if ((id & ~0) == 0) {cout << "Create Event Trigger Failed.\n";} else {/*** 将其加入调度队列* 不要被名字迷惑了* 这里仅仅是使其成为可调度状态,并不是立即执行* 以便doEventLoop()执行它*/scheduler->triggerEvent(id, (void*) taskFunData);}/*** 测试后台IO任务*/scheduler->setBackgroundHandling(STDOUT_FILENO, SOCKET_WRITABLE, &taskFun,(void*) taskFunData);/*** 延迟任务测试* 延迟5秒后执行*/scheduler->scheduleDelayedTask(5000000, delayedFun, (void*) taskFunData);/*** 启动任务调度*/env->taskScheduler().doEventLoop();return 0;
}