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. C++11 constexpr使用

    C++11为了提高代码执行效率做了一些改善.这种改善之一就是:生成常量表达式,允许程序利用编译时的计算能力.假如你熟悉模板元编程,你将发现constexpr使这一切变得更加简单.constexpr使我 ...

  2. 关于ddx/ddy重建法线在edge边沿上的artifacts问题

    经验证,原来ddx/ddy这两个操作,在forward rendering与deferred rendering中存在着微妙的应用区别. 在forward rendering中,GPU shader会 ...

  3. 【C/C++】标准IO操作

    fwrite实现文件复制 //@ author 成鹏致远 //@ net http://infodown.tap.cn //@ qq 552158509 //@ blog lcw.cnblogs.co ...

  4. 【转】一件有趣的事:我用 Python 爬了爬自己的微信朋友

    偶然了解到 Python 里的 itchat 包,它已经完成了 wechat 的个人账号 API 接口,使爬取个人微信信息更加方便. 于是乎玩心一起,打算爬一下自己的微信. 步骤核心: 网页启动not ...

  5. js用img代替ajax js心跳 向服务器定时传送参数 主要计算用户在线时长

    html: <!doctype html><html><head><meta charset="utf-8"><title&g ...

  6. csv和excel互转

    Python csv转换为excel学习笔记: openpyxl模块需要安装pip install openpyxl import openpyxl import csv '''读取csv文件写入ex ...

  7. linux清理Java环境

    1.清理Java环境rm -f /usr/bin/javarm -f /etc/alternatives/java rm -f /usr/bin/javacrm -f /etc/alternative ...

  8. app测试初窥

    要用到的两个神器:abd&drozer adb介绍 adb的全称为Android Debug Bridge,就是起到调试桥的作用,作为一名开发者倒是常用到这个工具.借助adb工具,我们可以管理 ...

  9. Drozer快速使用指南

    1.简介: Drozer是一款用于测试android应用程序漏洞的安全评估工具,能够发现多种类型的安全的漏洞,免费版本的相关资源下载地址: https://www.mwrinfosecurity.co ...

  10. Git CMD连接,管理(remote,add,commit,push)github repository

    git initmd testcd testgit statusgit add test  //git add test/a.txtgit status git remote add origin g ...