DSS源码分析

简介
    Darwin Streaming Server(简称DSS)是QuickTime Streaming Server开放式源代码的版本,同时支持FreeBSD、Linux、Solaris、Windows NT和Windows 2000等多个操作系统,是当前所有同类产品中支持平台最多的一个。DSS源代码完全采用标准C++语言写成,编程风格非常优秀,每个C++类都对应着一对和类同名的.h/.cpp文件。但是由于大量采用了面向对象的概念,如继承、多态等等;而且源文件和类相当多,所以不太容易讲清楚。
其中,最为重要的是基础功能类库(CommonUtilitiesLib)和流化服务器(StreamingServer)两个工程,前者是整个系统的通用代码工具箱,包括了线程管理、数据结构、网络和文本分析等多个功能模块。DSS和其他相关的工具使用基础功能类库工程中定义的功能类实现以下三个目标:
(1)抽象出系统中相同或类似的功能,用于降低代码的冗余度;
(2)封装基本功能,简化高层编码的复杂度;
(3)隔离开操作系统平台相关的代码。
而流化服务器工程中包含了DSS对多个国际标准的实现,是整个服务器的主工程。
基础功能类库
    最主要的数据结构有四种:哈希表(OSHashTable)、队列(OSQueue)、堆(OSHeap)和对象引用表(OSRef)。前三种是我们在编程中大量使用的数据结构,而对象引用表则是类似于COM/DCOM组件编程中IUNKOWN接口功能的数据结构,它首先为每个对象建立了一个字符串形式的ID,以便于通过这个ID找到对象(类似于QueryInterface);另外OSRef类还为每个对象实例建立了引用计数,只有一个对象不再被任何人引用,才可能被释放(类似于AddRef和Release)。

主体框架
DSS的核心服务器部分是由一个父进程所fork出的一个子进程构成,该父进程就构成了整合流媒体服务器。父进程会等待子进程的退出,如果在运行的时候子进程产生了错误从而退出,那么父进程就会fork出一个新的子进程。可以看出,网络客户和服务器直接的对接是由核心服务器来完成的。网络客户RTSPoverRTP来发送或者接受请求。服务器就通过模块来处理相应的请求并向客户端发送数据包。
核心流媒体服务通过创建四种类型的线程来完成自己的工作,具体如下:
服务器自己拥有的主线程:当服务器需要关闭检查,以及在关闭之前记录相关状态打印相关统计信息等任务处理时,一般都是通过这个线程来完成的。
空闲任务线程:这个任务线程是用来对一个周期任务队列的管理,主要管理两种任务,超时任务和Socket任务。
事件线程:套接口相关事件由事件线程负责监听,当有RTSP请求或者收到RTP数据包时,事件线程就会把这些实践交给任务线程来处理。
任务线程:任务线程会把事件从事件线程中取出,并把处理请求传递到对应的服务器模块进行处理,比如把数据包发送给客户端的模块,在默认情况下,核心服务器会为每个处理器核创建一个任务线程。

线程

        在Darwin Streaming Server中,除了主线程以外,有三种类型的线程,分别是TaskThread,EventThread以及IdleTaskThread:
1 TaskThread,TaskThread通过运行Task类型对象的Run方法来完成相应Task的处理。典型的Task类型是RTSPSession和RTPSession。TaskThread的个数是可配置的,缺省情况下TaskThread的个数与处理器的个数一致。等待被TaskThread调用并运行的Task放在队列或者堆中。
2 EventThread,EventThread负责侦听套接口事件,在Darwin Streaming Server中,有两种被侦听的事件,分别是建立RTSP连接请求的到达和RTSP请求的到达。对于RTSP连接请求的事件,EventThread建立一个RTSPSession,并启动针对相应的socket的侦听。对于RTSP请求的事件,EventThread把对应的RTSPSession类型的Task加入到TaskThread的队列中,等待RTSP请求被处理。
3 IdleTaskThread,IdleTaskThread管理IdleTask类型对象的队列,根据预先设定的定时器触发IdleTask的调度。TCPListenerSocket就是一个IdleTask的派生类,当并发连接数达到设定的最大值时,会把派生自TCPListenerSocket的RTSPListenerSocket加入到IdleTaskThread管理的IdleTask队列中,暂时停止对RTSP端口的侦听,直到被设定好的定时器触发。
Task类
        因为服务器从整体上采用了异步的运行模式,这就需要一种用于事件通信的机制。举例来说:一个RTSP连接对应的Socket端口监测到网络上有数据到达,此时必须有一个模块(或代码)被通知(notify)去处理这些数据。为此,DSS定义了Task及其相关类作为实现这一通信机制的核心。
        在Task.h/cpp文件中,定义了三个主要的类,分别是:任务线程池类(TaskThreadPool Class)、任务线程类(TaskThread Class)以及任务类(Task Class)。每个Task对象有两个主要的方法:Signal和Run。当服务器希望发送一个事件给某个Task对象时,就会调用Signal()方法;而Run()方法是在Task对象获得处理该事件的时间片后运行的,服务器中的大部分工作都是在不同Task对象的Run()函数中进行的。每个Task对象的目标就是利用很小的且不会阻塞的时间片完成服务器指定某个工作。
        基于Task类,定义了三种类型的Task,分别是IdleTask,TimeoutTask,以及普通的Task。
      Task类,TaskThread类,TaskThreadPool类:这三个类封装了线程库。三个类相互作用,通过Friend Class,使表现在外端的只有Task类,在这里,我们可以这样看:Task就是CPU使用片断,TaskThreadPool是CPU管理程序,TaskThread就是CPU。这样就使我们只需关心Task类就可,至于怎么调度,这是TaskThread及TaskThreadPool的事了。
下面我们来看这三个类是怎么互相作用从而形成一个整体的。
a. Virtual TaskThread::Entry():这个函数是整个线程函数的第一个执行函数,线程入口函数是TaskThread 从父类继承过来的Static OSThread::_Entry(),这个静态函数的唯一作用是执行Entry这个函数,这时的this指针是传进来的参数。这样就实现了线程的类封装。TaskThread::Entry()函数是一个循环,它不停的调用本身的WaitForTask函数,从自己内部的Queue中取得要运行的Task类指针。然后运行其中的Task:::Run()函数。
b. Virtual TaskThread::WaitForTask() 这个函数负责不停的从内部的队列中取到Task才返回。
c. static TaskThread **TaskThreadPool:: sTaskThreadArray. 这个变量是一个很重要的变量,通过它,我们就能在线程外与线程内通信,(要知道TaskThread的指针在线程类就是this指针,在线程外就是sTaskThreadArray的里面放的值。这样的话,我们就可以通过这个指针(或者说死了,也就是这个队列)来进行通讯。
d. void Task::Signal() 这个函数就是我们要往TaskThread队列里面加Task的函数.这个函数直接访问TaskThreadPool::sTaskThreadArray指针,并且因此直接访问到TaskThread的内部队列变量。
e. Virtual int Task::Run() 这个函数就是我们程序要运行的任务,也就是程序要在这里做什么。在这里程序的返回值是一个重要的信息,当返回-1时程序会直接删除该Task指针,当返回一个正数n 时线程会在 n毫秒后再次调用这个Task的Run函数,当返回0时,线程并不删掉这个Task指针,只是接着执行下一个Task.(注意; 在我们继承Task类,而重写Run虚函数时,我们第一件要作的事情是运行Task类的GetEvent()函数。)
在Task,TaskThread, TaskThreadPool 三个类的结构中,通过友元包装了TaskThread.TaskThreadPool,只留下了Task给我们重写,这是一种类的重要组合方法。
Task (OutThread) <-- Signal(function) ----- TaskThreadPool(OutThread) --> TaskThread(InThread)。

Socket类
    作为一个典型的网络服务器,DSS源代码中的Socket编程部分是其精华之一。DSS定义了一系列Socket类用于屏蔽不同平台在TCP/UDP编程接口和使用方法上的差异。DSS中的Socket类一般都采用异步模式的(即非阻塞的),而且能够向对应的Task对象传信(Signal)。Socket类中具有代表性的类是:EventContext、EventThread、Socket、UDPSocket、TCPSocket以及TCPListenerSocket等等。
    RTSP:首先,DSS定义了一个TCPListenerSocket类的子类RTSPListenerSocket,用于监听RTSP连接请求。RTSPListenerSocket类做的唯一一件事就是重载了GetSessionTask函数,当客户的连接请求到达后,它创建了一个Socket对象和RTSPSession对象的配对。RTSPSession对象是Task类的子类,是专门用于处理RTSP请求的任务类。
    RTP:RTP子系统是DSS中最为复杂的部分之一,这是因为发送RTP数据包的过程不但涉及到网络接口,而且和文件系统有着密切的关系。DSS的一个重要特征就是能够将线索化(Hinted)过的QuickTime文件通过RTSP和RTP协议流化出去。所有分析这些文件的代码都被提取出来并且封装在QTFile库中。这种封装方式使得系统的各个部分都变得简单:QTFile负责处理文件的分析;而DSS其他部分负责处理网络和协议。服务器中的RTPFileModule调用QTFile库检索索引过的QuickTime文件的数据包和元数据。

Module
流媒体服务器使用模块来响应各种请求及完成任务。有三种类型的模块:
(1).内容管理模块
媒体源相关的RTSP请求与响应,我们通过内容管理模块来管理,每个模块都用来对客户的需求进行解释并做相应处理,例如读取和解析模块支持的文件,或者请求的网络源信息,并通过RTP等方式响应。
内容管理模块有以下几个:
QTSSFileModule,
QTSSReflectorModule,
QTSSRelayModule,
QTSSMP3StreamingModule。
(2).服务器支持模块
服务器支持模块执行服务器数据的收集和记录功能。
服务器模块包括:
QTSSErrorLogModule,
QTSSAccessLogModule,
QTSSWebStatsModule,
QTSSWebDebugModule,
QTSSAdminModule,
QTSSPOSIXFileSystemModule。
(3).访问控制模块
访问控制模块提供鉴权和授权功能,以及操作URL路径提供支持。
访问控制模块包括:
QTSSAccessModule,
QTSSHomeDirectoryModule,
QTSSHttpFileModule,
QTSSSpamDefenseModule。

        在DSS中的模块分为动态模块和静态模块,动态模块在服务器启动时会首先装载动态模块,之后才会装载一部分静态模块。我们一般建议将我们自己书写的功能模块编译为动态模块来替换或扩展现有的服务器模块,因为它会被优先装载。在QTSS的模块中必须包含Register这个角色,这也是每个模块所必须支持的角色。在模块被装载之后服务器会调用每个模块的Register角色。在这个角色当中,模块会调用QTSS_AddRole函数来记录这个模块所支持的其他角色。然后服务器就将初始化角色来调用每一个拥有这个角色的模块。这个角色主要是做一些初始化的任务,比如说内存的分配或者对数据结构的初始化等等。在关闭服务器的时候,所有模块的Shutdown角色将被调用,这个角色主要是为了结束工作后处理现场,比如释放内存等等。流媒体服务器主要就是通过这种角色来完成相应任务的。
Module开发流程
        作为一个运行于多个操作系统平台的开发源代码的服务器,DSS提供了一种称为Module的二次开发接口。使用这个开发接口,我们可以充分利用服务器的可扩展性及其实现的多种协议,并且能够保证和将来版本兼容。DSS中的许多核心功能也是以Module的方式预先实现并且编译的,因此可以说对Module的支持已经被设计到DSS的内核中去了。
       下面我们将分析DSS的一个内嵌Module:QTSSFileModule的源代码来说明Module的编程方式,QTSSFileModule的实现在QTSSFileModule.cpp文件中。
       每个QTSS Module必须实现两个函数:
首先,每个QTSS Module必须实现一个主函数,服务器调用该函数用于启动和初始化模块中的QTSS函数;QTSSFileModule主函数的实现如下:
QTSS_Error QTSSFileModule_Main(void* inPrivateArgs)
{
       return _stublibrary_main(inPrivateArgs, QTSSFileModuleDispatch);
}
其中QTSSFileModuleDispatch是Module必须实现的分发函数名。
另一个需要实现的是分发函数,服务器调用该函数实现某个特殊任务。此时,服务器将向分发函数传入任务的名字和一个任务相关的参数块。QTSSFileModule分发函数的实现如下:
QTSS_Error QTSSFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock)
{     //根据传入的任务名称和入参执行相应的处理函数
       switch (inRole)      //任务名称
       {
              case QTSS_Register_Role:
                     return Register(&inParamBlock->regParams);
              case QTSS_Initialize_Role:
                     return Initialize(&inParamBlock->initParams);
              case QTSS_RereadPrefs_Role:
                     return RereadPrefs();
              case QTSS_RTSPRequest_Role:
                     return ProcessRTSPRequest(&inParamBlock->rtspRequestParams);
              case QTSS_RTPSendPackets_Role:
                     return SendPackets(&inParamBlock->rtpSendPacketsParams);
              case QTSS_ClientSessionClosing_Role:
                     return DestroySession(&inParamBlock->clientSessionClosingParams);
       }
       return QTSS_NoErr;
}
       其中,分发函数的入参是一个联合,它根据任务名称的不同,具体的数据结构也不同,下面是该数据结构的定义:
       typedef union
{
              QTSS_Register_Params                             regParams;
              QTSS_Initialize_Params                            initParams;
              QTSS_ErrorLog_Params                           errorParams;
              //此处略去其他多个数据结构…
} QTSS_RoleParams, *QTSS_RoleParamPtr;
       DSS提供了两种方式把我们自己开发的Module添加到服务器中:一种称为静态模块(Static Module),该方式将我们开发的Module代码直接编译到内核中去;另一种称为动态模块(Dynamic Module),该方式将我们开发的Module单独编译称为一个动态库,然后修改配置,使服务器在启动时将其加载。

参考: http://blog.csdn.net/haolipengzhanshen/article/details/39056317
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值