织梦CMS - 轻松建站从此开始!

罗索

Apple公司Darwin流式服务器源代码分析2

罗索客 发布于 2009-04-23 11:36 点击:次 
Darwin Streaming Server(简称DSS)是QuickTime Streaming Server开放式源代码的版本,同时支持FreeBSD、Linux、Solaris、Windows NT和Windows 2000等多个*作系统,是当前所有同类产品中支持平台最多的一个。
TAG:

2 Darwin流化服务器介绍

Darwin Streaming Server(简称DSS)是QuickTime Streaming Server开放式源代码的版本,同时支持FreeBSD、Linux、Solaris、Windows NT和Windows 2000等多个*作系统,是当前所有同类产品中支持平台最多的一个。

DSS的源代码和相关文档可从以下站点获得:http://www.apple.com DSS源代码完全采用标准C++语言写成,编程风格非常优秀,每个C++类都对应着一对和类同名的.h/.cpp文件。但是由于大量采用了面向对象的概念,如继承、多态等等;而且源文件和类相当多,所以不太容易讲清楚。

因此,读者最好事先把代码完整的过滤一两遍,再配合本文,就能看得更清楚点。整个服务器包括多个子系统,分别存放在独立的工程内,如图2所示。

其中,最为重要的是基础功能类库(CommonUtilitiesLib)和流化服务器(StreamingServer)两个工程,前者是整个系统的通用代码工具箱,包括了线程管理、数据结构、网络和文本分析等多个功能模块。DSS和其他相关的工具使用基础功能类库工程中定义的功能类实现以下三个目标:
(1)抽象出系统中相同或类似的功能,用于降低代码的冗余度;
(2)封装基本功能,简化高层编码的复杂度;
(3)隔离开*作系统平台相关的代码。而流化服务器工程中包含了DSS对多个国际标准的实现,是整个服务器的主工程。在本文中,我们将重点分析这两个工程中的核心代码和模块。
另外,我们还将简单介绍利用DSS提供的开发接口(Module)扩展和定*务器的方法。
DSS实现了四种IETF制定的国际标准,分别是:实时流传输协议RTSP(Real-time Streaming Protocol, RFC 2326)、实时传输协议(RTP Real-time Transfer Protocol,RFC 1889)、实时传输控制协议RTCP(Real-time Transport Control Protocol,RFC 1889)、会话描述协议SDP(Session Description Protocol,RFC 2327)。

这四个标准是开发所有流式媒体产品都必须掌握的,因此在对相关代码进行分析和二次开发之前,希望读者了解上述四种协议的基本思想,上述协议样本可从以下网站获得:http://www.ietf.org

3 基础功能类库(Common Utilities)

3.1 OS类
Darwin Streaming Server支持包括Windows,Linux以及Solaris在内的多种*作系统平台。我们知道,Windows和Unix(或Unix-like)*作系统之间无论从内核还是编程接口上都有着本质的区别,即使是Linux和Solaris,在编程接口上也大为不同。为此,DSS开发了多个用于处理时间、临界区、信号量、事件、互斥量和线程等*作系统相关的类,这些类为上层提供了统一的使用接口,但在内部却需要针对不同的*作系统采用不同的方法实现。表2罗列出了DSS中的主要OS类和数据结构。
表2 DSS中的主要OS类和数据结构
类(数据结构)名 主要功能
OS 平台相关的功能类,如内存分配、时间等
OSCond 状态变量的基本功能和*作
OSMutex 互斥量的基本功能和*作
OSThread 线程类
OSFileSource 简单文件类
OSQueue 队列类
OSHashTable 哈希表类
OSHeap 堆类
OSRef 参考引用类
3.1.1 OSMutex/OSCond Class
在有多个线程并发运行的环境中,能同步不同线程的活动是很重要的,DSS开发了OSMutex和OSCond两个类用以封装不同*作系统对线程同步支持的差异。
我们首先分析OSMutex类,这个类定义了广义互斥量的基本*作,类定义如下:

class OSMutex
{
public:
OSMutex(); //构造函数
~OSMutex(); //析构函数

inline void Lock(); //加锁
inline void Unlock(); //解锁
inline Bool16 TryLock(); //异步锁,无论是否成功立即返回

private:
#ifdef __Win32__
CRITICAL_SECTION fMutex; //临界区
DWORD fHolder; //拥有临界区的线程id
UInt32 fHolderCount; //进入临界区线程数
//其他略…
}

在Windows平台上,OSMutex类是通过临界区(CRITICAL_SECTION)来实现的,第10行定义了临界区变量fMutex。类实例化时构造函数调用InitializeCriticalSection(&fMutex)初始化临界区变量,对应的在析构函数中调用DeleteCriticalSection(&fMutex)清除。
Lock()函数用于对互斥量加锁,它调用私有方法RecursiveLock实现:

void OSMutex::RecursiveLock()
{
// 当前线程已经拥有互斥量,只需增加引用计数
if (OSThread::GetCurrentThreadID() == fHolder)
{
fHolderCount++; //增加引用计数
return;
}
#ifdef __Win32__
::EnterCriticalSection(&fMutex); //申请进入临界区
#else
(void)pthread_mutex_lock(&fMutex);
#endif
Assert(fHolder == 0);
fHolder = OSThread::GetCurrentThreadID(); //更新临界区拥有者标志
fHolderCount++;
Assert(fHolderCount == 1);
}

第1行检测如果当前线程已经拥有互斥量,就只需将内部计数fHolderCount加1,以便纪录正在使用互斥量的方法数。如果当前线程还没有得到互斥量,第7行调用EnterCriticalSection()函数申请进入临界区;如果当前已经有其他线程进入临界区,该函数就会阻塞,使得当前线程进入睡眠状态,直到占用临界区的线程调用LeaveCriticalSection(&fMutex)离开临界区后才可能被唤醒。一旦线程进入临界区后,它将首先更新临界区持有者标志(第12行),同时将临界区引用计数加1。
注意到另外一个函数TryLock(),该函数也是用于为互斥量加锁,但与Lock()不同的是,TryLock()函数为用户提供了异步调用互斥量的功能,这是因为它调用::TryEnterCriticalSection(&fMutex)函数申请进入缓冲区:如果临界区没有被任何线程拥有,该函数将临界区的访问区给予调用的线程,并返回TRUE,否则它将立刻返回FALSE。TryEnterCriticalSection()和EnterCriticalSection()函数的本质区别在于前者从不挂起线程。
接着分析OSCond类,该类定义了状态变量(Condition Variable)的基本*作,类定义如下:

class OSCond
{
public:
OSCond(); //构造函数
~OSCond(); //析构函数

inline void Signal(); //传信函数
inline void Wait(OSMutex* inMutex, SInt32 inTimeoutInMilSecs = 0);
//等待传信函数
inline void Broadcast(); //广播传信函数

private:
#ifdef __Win32__
HANDLE fCondition; //事件句柄
UInt32 fWaitCount; //等待传信用户数
//其他略…
}

虽然同是用于线程同步,但OSCond类与OSMutex大不相同,后者用来控制对关键数据的访问,而前者则通过发信号表示某一*作已经完成。在Windows平台中,OSCond是通过事件(event)来实现的;构造函数调用CreateEvent()函数初始化事件句柄fCondition,而析构函数则调用CloseHandle()关闭句柄。
OSCond的使用流程是这样的:线程调用Wait(OSMutex* inMutex, SInt32 inTimeoutInMilSecs = 0)函数等待某个事件的发生,其中inTimeoutInMilSecs是最长等待时间,0代表无限长。Wait()函数内部调用了WaitForSingleObject (fCondition, theTimeout)函数,该函数告诉系统线程在等待由事件句柄fCondition标识的内核对象变为有信号,参数theTimeout告诉系统线程最长愿意等待多少毫秒。如果指定的内核对象在规定时间内没有变为有信号,系统就会唤醒该线程,让它继续执行。而函数Signal()正是用来使事件句柄fCondition有信号的。Signal()函数内部实现很简单,只是简单调用SetEvent函数将事件句柄设置为有信号状态。
使用OSCond的过程中存在一种需求,就是希望通知所有正在等待的用户事件已经完成,而Signal()函数每次只能通知一个用户,因此又开发了另外一个广播传信函数如下:

inline void OSCond::Broadcast()
{ //提示:本函数相当循环调用Signal()函数
#ifdef __Win32__
UInt32 waitCount = fWaitCount; //等待传信的用户数
for (UInt32 x = 0; x < waitCount; x++) //循环为每个用户传信
{
BOOL theErr = ::SetEvent(fCondition); //设置事件句柄为有信号状态
Assert(theErr == TRUE);
}
//此处略…
}

Broadcast首先统计所有等待传信的用户数(第2行),然后用一个循环为每个用户传信(第3~7)行。这种编程方法虽然不是很优雅(elegant),但是由于Windows平台上不支持广播传信功能(Linux和Solaris均支持),也只好如此。
3.1.2 OSThread Class
OSThread是DSS中最重要的类之一,它封装并且定义了使用线程的方式,因此需要重点讨论。OSThread类的定义如下:

class OSThread
{
public:
// 必须在使用其他OSThread函数前调用该初始化函数
static void Initialize();

OSThread(); //构造函数
virtual ~OSThread(); //析构函数

//子类继承该纯虚函数完成自己的工作
virtual void Entry() = 0;

void Start(); //启动线程
void Join(); //等待线程运行完成后删除
void Detach(); //使线程处于fDetached状态
static void ThreadYield(); //Windows平台不用
static void Sleep(UInt32 inMsec); //让线程睡眠

private:
//标识线程的状态
Bool16 fStopRequested:1;
Bool16 fRunning:1;
Bool16 fCancelThrown:1;
Bool16 fDetached:1;
Bool16 fJoined:1;

static void CallEntry(OSThread* thread);//调用子类重载的虚函数
#ifdef __Win32__
//使用_beginghreadex创建线程时的标准入口函数
static unsigned int WINAPI _Entry(LPVOID inThread);
#else
static void* _Entry(void* inThread); //unix下的入口函数
#endif
}

OSThread封装了线程的基本功能,一个OSThread的实例代表一个线程。用户通过继承OSThread,并且重载其中的纯虚函数Entry(第5行),从而将自己的任务交给该线程运行。OSThread内部运行机制比较复杂,为此我们用图3所示的流程来描述其运行过程。
另外,OSThread对于线程的状态定义了一套完整的控制方法。当用户调用start()函数后,按照上图,最终将调用CallEntry()函数,而该函数在调用Entry()之前将线程设定为运行状态(thread->fRunning = true),当Entry()函数运行完后再设为非运行状态;在运行过程中,用户可以通过StopAndWaitForThread()、join()、Detach()以及ThrowStopRequest()等函数改变线程其他状态变量。
3.1.3 OSHashTable/OSQueue/OSHeap/OSRef Class
DSS定义了几个通用的较为复杂的数据结构,它们都以类的方式封装。这些数据结构不但贯穿于DSS的所有源代码,而且由于其封装的十分好[FS:PAGE],读者可以在看懂源代码的基础上很容易的将它们从DSS的工程中抽取出来,构建自己的基础类库,为将来的开发工作打下良好的基础。另外,对这些基础数据结构源代码的研究将提高我们对于面向对象技术的掌握和领会。
最主要的数据结构有四种:哈希表(OSHashTable)、队列(OSQueue)、堆(OSHeap)和对象引用表(OSRef)。前三种是我们在编程中大量使用的数据结构,而对象引用表则是类似于COM/DCOM组件编程中IUNKOWN接口功能的数据结构,它首先为每个对象建立了一个字符串形式的ID,以便于通过这个ID找到对象(类似于QueryInte*ce);另外OSRef类还为每个对象实例建立了引用计数,只有一个对象不再被任何人引用,才可能被释放(类似于AddRef和Release)。
鉴于这几个类在结构上有相似之处,下面我们将分析OSHashTable的源代码,以便能够帮助读者更好的理解其他几个类。OSHashTable的代码如下:

template<class T, class K>
class OSHashTable {
/*提示:OSHashTable被设计成为一个类模版,两个输入参数分别为:class T:实际的对象类;class K:用于为class T计算哈希表键值的功能类。*/
public:
OSHashTable( UInt32 size ) //构造函数,入参是哈希表中对象的最大个数
{
fHashTable = new ( T*[size] ); //申请分配size个哈希对象class T的空间
Assert( fHashTable );
memset( fHashTable, 0, sizeof(T*) * size ); //初始化
fSize = size;
/*下面的代码决定用哪种方式为哈希表的键值计算索引;
如果哈希表的大小不是2的幂,只好采用对fSize求余的方法;
否则可以直接用掩码的方式,这种方式相对速度更快*/
fMask = fSize - 1;
if ((fMask & fSize) != 0) //fSize不是2的幂
fMask = 0;
fNumEntries = 0; //当前对象数
}
~OSHashTable() //析构函数
{
delete [] fHashTable; //释放空间
}
//下面介绍向哈希表中添加一个class T对象的源代码
void Add( T* entry ) {
Assert( entry->fNextHashEntry == NULL );
/*利用功能类class K,计算class T对象的哈希键值,其计算方法由用户在class K中定义*/
K key( entry );
UInt32 theIndex = ComputeIndex( key.GetHashKey() );//利用键值计算索引
entry->fNextHashEntry = fHashTable[ theIndex ]; //在新加对象中存储索引值
fHashTable[ theIndex ] = entry; //将该对象插入到索引指定的位置
fNumEntries++; /
}
//下面介绍从哈希表中删除一个class T对象的源代码
void Remove( T* entry )
{
//首先从哈希表中找到待删除的对象
//1、计算哈希键值和其对应的对象索引
key( entry );
UInt32 theIndex = ComputeIndex( key.GetHashKey() );
T* elem = fHashTable[ theIndex ];
T* last = NULL;
/*2、通过对象索引查找对象,如果不是要找的对象,接着找下一个,直到找到为止。这是因为,存放的时候就是按照这种模式计算索引的。*/
while (elem && elem != entry) {
last = elem;
elem = elem->fNextHashEntry;
}
//找到该对象,将其删除
if ( elem )
{
Assert(elem);
if (last)
last->fNextHashEntry = elem->fNextHashEntry;
else //elem在头部
fHashTable[ theIndex ] = elem->fNextHashEntry;
elem->fNextHashEntry = NULL;
fNumEntries--;
}
}
//下面介绍从哈希表中查找一个class T对象的方法
T* Map( K* key ) //入参为哈希键值
{
UInt32 theIndex = ComputeIndex( key->GetHashKey() ); //计算索引
T* elem = fHashTable[ theIndex ]; //找到索引对应的对象
while (elem) {
K elemKey( elem );
if (elemKey =*key) //检查是否找对
break;
elem = elem->fNextHashEntry; //如果不是,继续找下一个
}
return elem;
}
//以下略…
}

以上介绍了哈希表的构造以及三种基本*作:添加、删除和查询。另外,DSS还定义了OSHashTableIter类用于枚举OSHashTable中的class T对象;其中主要的*作有First和Next等,限于篇幅,此处就不再详述。

(佚名)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www.rosoo.net/a/200904/7279.html]
本文出处:网络博客 作者:佚名
顶一下
(0)
0%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容