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

(转)live555学习笔记-UsageEnvironment和TaskScheduler的更多相关文章

  1. (转)live555学习笔记10-h264 RTP传输详解(2)

    参考: 1,live555学习笔记10-h264 RTP传输详解(2) http://blog.csdn.net/niu_gao/article/details/6936108 2,H264 sps ...

  2. (转)live555学习笔记9-h264 RTP传输详解(1)

    九 h264 RTP传输详解(1) 前几章对Server端的介绍中有个比较重要的问题没有仔细探究:如何打开文件并获得其SDP信息.我们就从这里入手吧. 当RTSPServer收到对某个媒体的DESCR ...

  3. (转)live555学习笔记7-RTP打包与发送

    七 RTP打包与发送 rtp传送开始于函数:MediaSink::startPlaying().想想也有道理,应是sink跟source要数据,所以从sink上调用startplaying(嘿嘿,相当 ...

  4. live555源码学习笔记之TaskScheduler

    今天抽空研究了下live555的任务实现: TaskScheduler分为三种任务:socket handler,event handler,delay task.这三种任务的特点是,前两个加入执行队 ...

  5. 开源项目live555学习心得

      推荐:伊朗美女找丈夫比找工作难女人婚前一定要看清三件事 × 登录注册   疯狂少男-IT技术的博客 http://blog.sina.com.cn/crazyboyzhaolei [订阅][手机订 ...

  6. 【WPF】学习笔记(三)——这个家伙跟电子签名板有个约定

    这篇博客依旧是以电子签名板为基础而展开的,主要是对前文([WPF]学习笔记(一)--做一个简单的电子签名板)存在的部分问题进行解释,以及部分小功能的添加.由于这篇博客是建立在学习笔记一的基础上的,所以 ...

  7. live555学习(一)通读Makefile编译live555

    live555学习(一)通读Makefile编译live555 live555 编译live555 学习开源 live555学习(一)通读Makefile编译live555 前言 live555简介 ...

  8. 基于.net的分布式系统限流组件 C# DataGridView绑定List对象时,利用BindingList来实现增删查改 .net中ThreadPool与Task的认识总结 C# 排序技术研究与对比 基于.net的通用内存缓存模型组件 Scala学习笔记:重要语法特性

    基于.net的分布式系统限流组件   在互联网应用中,流量洪峰是常有的事情.在应对流量洪峰时,通用的处理模式一般有排队.限流,这样可以非常直接有效的保护系统,防止系统被打爆.另外,通过限流技术手段,可 ...

  9. Learning hard 学习笔记

    第一章 你真的了解C#吗 1.什么是C#, 微软公司,面向对象,运行于.NET Framework之上, 2.C#能编写哪些应用程序, Windows应用桌面程序,Web应用程序,Web服务, 3.什 ...

随机推荐

  1. iOS开发之按钮的基本使用

    实现功能: 点击向上的箭头,按钮图片向上,移动,点击向下的箭头,按钮图片向下移动 点击向左的箭头,按钮图片向左移动,点击向右的箭头,按钮图片向右移动, 点击加号图片放大,点击减号,图片缩小 第一步: ...

  2. DIOCP3-数据库DEMO

    socket-Coder\DataModuleDEMO\   本DEMO演示数据库的简单使用,其他功能需要自己扩展.   将工程的输出路径设置到socket-Coder\DataModuleDEMO\ ...

  3. 【Android】事件处理系统

    linux输入子系统 Android是linux内核的,所以它的事件处理系统也在linux的基础上完成的. Linux内核提供了一个Input子系统来实现的,Input子系统会在/dev/input/ ...

  4. Asp.Net微信支付接口之jsApiPay教程

      1.微信支付JsApiPay只能在微信浏览器中使用 2.必须先申请微信支付功能 3.设置域名 4.设置支付授权目录 一定要精确到最后一级目录 5.获取APPID和AppSecret AppSecr ...

  5. libevent源码分析:event_add、event_del

    event_add.event_del两个函数分别是使event生效和失效的,下面就来看一下两个函数的实现. event_add int event_add(struct event *ev, con ...

  6. C#使用ICSharpCode.SharpZipLib压缩后进行web批量下载文件

    参考:http://blog.csdn.net/kongwei521/article/details/51167903#

  7. MySQL的MVCC

    基本概念 Multi-Version Concurrency Control 多版本并发控制,MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问:在编程语言中实现事务内存. ...

  8. FutureTask 源码分析

    FutureTask 源码分析,这个类的原理与我分析android当中的FutureTask类差不多[http://www.cnblogs.com/daxin/p/3802392.html] publ ...

  9. Java编程的逻辑 (73) - 并发容器 - 写时拷贝的List和Set

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  10. DRUPAL8模版命名规则

    DRUPAL8模版命名规则   xiaopang 星期一, 04/08/2013 - 20:04 发布 我想了半个小时,想怎么清楚简单的为大家解释drupal8的模版命名规则,但是很显然,我失败了,但 ...