(转)live555学习笔记-UsageEnvironment和TaskScheduler

2011-12-6阅读1264 评论1

一直想学习流媒体服务器的设计,这几天有点时间,看了一下live555的源代码。live555是一个开源的跨平台流媒体服务器,使用编程语言是C++。将现阶段学习笔记总结如下,其实关键是要弄明白几个类的作用和它们之间的关系:

一.UsageEnvironment类以及其派生类的继承关系

基类UsageEnvironment是一个抽象类,它主要是定义了一些接口函数(纯虚函数)包括错误代码/结果消息系列函数,重载输出操作符系列函数等;以及定义重要的数据成员fScheduler,它声明为TaskScheduler的引用;它重要是因为它是整个程序运作的引擎。为了做更好的封装UsageEnvironment将构造函数和析构函数声明为protected。后面会看到,UsageEnvironment的派生类的构造是通过静态函数creatNew实现的,而析构都是通过调用的reClain()函数实现。

 

BasicUsageEnvironment0很简单,它定义了一个字符数组来存储结果消息,只是实现了UsageEnvironment中的结果消息系列函数,所以它仍然是一个抽象类。

 

BasicUsageEnvironment也很简单,它实现了输出操作符系列函数的重载以及静态函数creatNew。

 

因此只需要记住这三样东西:结果消息函数、输出操作符函数、TaskScheduler引用变量,便可以了解UsageEnvironment及其派生类的作用。实际上完全可以分开三个类来表示,作者可能是为了方便使用才把它们放到一起。因为当我们要给程序增加功能时,可以在TaskScheduler完成,而想设置和输出程序运行期间的消息,可以直接使用输出操作符和结果消息处理函数,它给后续程序开发提供一个很好的环境。

 

二.TaskScheduler类以及其派生类的继承关系

顾名思义,TaskScheduler是一个任务调度器。它的继承关系图跟UsageEnvironment类似,呵呵,有了前面的分析我们也应该很容易掌握这个类。这里的任务是抽象的,可以想象为一段代码或一个函数,任务调度目的就是要决定程序当前应该运行哪一个任务。

 

1.TaskScheduler是一个抽象基类,它定义了一系列的接口函数,其中doEventLoop定义为程序循环函数。根据任务的类别,作者分成三类的来处理,每一次循环都会按照下面顺序来完成调用(请参考BasicTaskScheduler中的singleStep函数):

(1)首先处理的是Socket Event,负责I/O复用,使用select函数等待指定的描述字准备好读、写或有异常条件处理。若select返回值大于-1,则转到相应的处理函数;否则表明发生异常,程序将转到错误处理代码中去。该类型适合于有I/O操作的任务。

相关函数:setBackgroundHandling/disableBackgroundHandling/moveSocketHandling 

(2)接着是处理触发器事件(Trigger-Event)。作者定义了一个32位的位图来实现触发事件,当某一位设置为1则表明要触发该位对应的事件。若同时有多个(3个或以上)触发事件,它们触发的先后还会跟事件创建的先后有关,因此这一类型仅适合于没有顺序依赖关系的任务。
相关函数:createEventTrigger/deleteEventTrigger/triggerEvent

(3)最后一个是延迟任务(Delayed Task),它是一个带有时间的任务。当剩余时间不为0,则任务不执行。通过调整任务的剩余时间,可以灵活地安排任务。
相关函数:scheduleDelayedTask/unscheduleDelayedTask/rescheduleDelayedTask

TaskScheduler为了兼容以前的程序,还保留了turnOnBackgroundReadHandling/turnOffBackgroundReadHandling函数 ,实际上它们也是通过调用setBackgroundHandling/disableBackgroundHandling实现的。当然还有一个错误处理函数interalError,处理程序错误,派生类可重载。

2.BasicTaskScheduler0主要是实现了触发事件和延迟任务。

(1)触发事件是通一个32位图实现的,它利用两个数组存储存储触发事件的函数指针和函数参数指针。它是从最高为开始存放的,即最高位对应函数指针数组和参数指针数组的第0个元素,最多可使用32个触发器。它还保存上一次的触发的序号和触发mask,作为下一次起始点,从而保证所有的触发器都能够触发。

(2)延迟任务是通过一个双向循环链表实现的。它的节点实际是AlarmHandler,而链表则实现为DelayQueue,它们都是从DelayQueueEntry基类继承得到的,三者间的关系如下图:

DelayQueueEntry可看作是一个抽象的双向链表中的节点,除了前向指针和后向指针,它附加了一个fDeltaTimeRemaining成员和fToken成员,表示延时时间和标识节点的唯一标志。此外还定义了有一个TimeOut时调用的虚函数handleTimeOut()。

AlarmHandler是实际的延时任务节点,非常简单的,它在DelayQueueEntry基础上定义了一个的函数指针和函数参数指针,并重载了handleTimeOut函数。

DelayQueue是作为循环链表类,一般来说不用继承DelayQueueEntry,作者在这里是把它作为循环链表的头节点。其余的都是循环链表的常规操作,包括节点的查询、插入、删除、更新操作。DelayQueue还实现了timeToNextAlarm()返回头节点的时延,以及handleAlarm()实际延时任务处理,实际调用的是节点的handleTimeOut()函数;

3.BasicTaskScheduler类实现剩下的I/O操作任务接口和三类任务的实际调度(singleStep函数)。

I/O任务的实现也很简单的,它也是使用双向循环链表来存储任务。它的节点定义为HandlerDiscriptor,包括前向后向节点指针,函数指针和函数参数指针,以及IO相关的socketNum和conditionSet数据成员。循环链表类定义为HandlerSet,类似地也定义了查询、插入、删除、更新操作。它声明了一个节点成员作为头节点。

三.对UsageEnvironment的测试

现在可以测试体验一下这些类的功能,我写了一个简单的程序,设置三种类型任务并进行调度。代码如下:
// This is a test for basic objects, such as TaskSchduler, UsageEnvironment and
// so on. It's just for study purpose.

#include <BasicUsageEnvironment.hh>
#include <iostream>
using namespace std;

TaskScheduler* scheduler = BasicTaskScheduler::createNew();
UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);

void taskFunc(void* clientData){
	cout<<"taskFunc(\""<<(char*)clientData<<"\") called."<<endl;
}

void handlerFunc(void* clientData, int mask){
	cout<<"handlerFunc(\""<<(char*)clientData<<"\", "<<mask
		<<") called."<<endl;
	scheduler->disableBackgroundHandling(STDOUT_FILENO);
}

int main(int argc, char* args[])
{
        // IO event test
        char handlerClientData[] = "IO Event";
        scheduler->setBackgroundHandling(STDOUT_FILENO, SOCKET_WRITABLE,
	(TaskScheduler::BackgroundHandlerProc*)&handlerFunc, handlerClientData);       
	
	// trigger event test
        EventTriggerId id1 = scheduler->createEventTrigger(taskFunc);
        char triggerClientData1[] = "Trigger Event 1";
        EventTriggerId id2 = scheduler->createEventTrigger(taskFunc);
        char triggerClientData2[] = "Trigger Event 2";
        EventTriggerId id3 = scheduler->createEventTrigger(taskFunc);
        char triggerClientData3[] = "Trigger Event 3";
	(*env)<<"Setting Event triggers...\n";
	scheduler->triggerEvent(id2, (void*)triggerClientData2);  
        scheduler->triggerEvent(id1, (void*)triggerClientData1);
        scheduler->triggerEvent(id3, (void*)triggerClientData3);
        (*env)<<"Event triggers has been set.\n";

	// delayed task test
	char delayedTaskClientData1[] = "Delayed Task 1s";
	TaskToken token1 = scheduler->scheduleDelayedTask(1000000,
					taskFunc, delayedTaskClientData1);
	char delayedTaskClientData2[] = "Delayed Task 5s";
        TaskToken token2 = scheduler->scheduleDelayedTask(5000000,
                                        taskFunc, delayedTaskClientData2);

	// loop
	scheduler->doEventLoop();

	return 0;
}
编译执行,输出的结果是:
Setting Event triggers...
Event triggers has been set.
handlerFunc("IO Event", 4) called.
taskFunc("Trigger Event 1") called.
taskFunc("Trigger Event 2") called.
taskFunc("Trigger Event 3") called.
taskFunc("Delayed Task 1s") called.
taskFunc("Delayed Task 5s") called.
 
代码能够正常工作。

总结:

live555使用UsageEvironment类和TaskSheduler类以及它们的派生类建立一个良好的程序开发基本架构,在此基础上可以方便构建我们的各种应用。一个任务只需要两步就可以完成,先根据任务的类型定义任务处理函数,然后将任务添加到循环体里面即可。

值得注意的是这个任务调度器的性能和线程安全问题。如果某个任务需要长时间的处理或发生阻塞,那么主循环也将发生阻塞,其他的任务将得不到及时的响应,因此设计任务时要考虑任务花费的时间,若太长则要考虑是否开辟另外一个线程来处理了。另外,从源代码上来看,该任务调度器并没有为支持多线程做更多的工作,所以通过多个线程添加任务,可能会发生异常。

转自:http://m.blog.csdn.net/blog/huangwanzhang/7042843

posted @ 2015-06-01 19:51  lihaiping  阅读(1941)  评论(1编辑  收藏  举报