




已阅读5页,还剩22页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
概述当前,伴随着Internet的飞速发展,计算机网络已经进入到每一个普通人的家庭。在这个过程中,一个值得我们关注的现象是:Internet中存储和传输内容的构成已经发生了本质的改变,从传统的基于文本或少量图像的主页变为大容量、富信息量的流式媒体信息。一份早在1998年提交的研究报告就曾指出,流式媒体统治Internet的潮流是不可抗拒的,该报告估计到2003年,存储在网络服务器上的内容超过50%的将是流式媒体信息。但今天看来,这个估计还是有些保守了。所谓的流式媒体简单的讲就是指人们通过网络实时的收看多媒体信息:如音频流、视频流等。与流式媒体对应的传统工作方式是下载+播放模式,即用户首先下载多媒体文件,然后再在本地播放,这种方法的一个主要缺点是启动延迟较大,例如一个30分钟长的MPEG-I文件(相当于VCD质量),即使使用1.5Mbps的速率下载,也需要半个小时才能完成,这样一个漫长的等待时间实在是无法忍受。在窄带网络环境中,几乎所有基于Internet的流式媒体产品都有着类似的工作原理:首先需要开发高效的压缩编码技术,并通过一套完整有效的传输体系将其发布到用户的桌面上。目前在流式媒体领域,有三种占有主导地位的产品,它们分别是Apple公司的Quick Time、Microsoft公司的Media Server以及Real公司的Real System。本文将介绍QuickTime技术及其开放源代码的Darwin流化服务器。1 QuickTime技术介绍Apple公司近日发布了QuickTime 5及QuickTime Streaming Server 3(简称QTSS)。作为客户端的QuickTime 5是用于在Internet上对高质量音频和视频内容进行创建、播放及提供数字流的软件,目前QuickTime在全世界的使用量已经超过1亿5千万份。QuickTime Streaming Server 3是Apple基于标准的、开放式源代码的流式服务器软件的新版本,它包括以下新功能:跳读保护(Skip Protection),一项获得专利的特性组合,它可以保证Internet上数字流的质量,防止中断;全新的易于使用、基于Web的界面,用户可以在本地或远程进行管理,实现服务器配置。作为Internet流媒体联盟(ISMA)的创建者之一,Apple不断致力于开发符合业界标准的产品和技术,通过提高互*作性来优化用户的使用体验,目前QuickTime已被国际标准组织(ISO)选为MPEG-4的基本文件格式,可预见Apple将有更多MPEG-4 产品和技术的推出。QuickTime正迅速成为世界领先的跨平台多媒体技术,而且是迄今为止唯一的开放式源代码、基于标准的数字流解决方案。ZDNet在2000年9月对于三种流式媒体服务器的特征比较说明了QTSS不仅仅被技术开发者关注,而且可以通过简单的定制成为成熟强大的产品,评测结果可见表1。表1ZDNet对三类产品的评测结果服务器模块 QTSS 2.01 Media Server 7 RealServer Basic 7*作系统支持 Windows NT, 2000; FreeBSD; Linux; Mac OS; Solaris Windows NT, 2000 Windows NT, 2000并发流个数 2,000 2,000 25 free/3000 pro现场直播和广播 Yes Yes Yes在线广告支持Yes Yes YesPPV/流加密 No / No Yes / Yes Yes / Yes分配流能力 No Yes YesSMIL标准支持 Yes No YesRTSP标准支持 YesNo Yes多播支持 Yes Yes Yes状态报告 Yes Yes Yes服务器日志 Yes Yes Yes防火墙和代理支持 Yes Yes Yes远程监控 Yes Yes Yes客户可以使用QuickTime Player或其他支持QuickTime的应用程序在Windows或Macintosh平台上接收视频流,而且QuickTime Player可以从苹果公司的网站上下载免费使用。如果安装了QuickTime的插件,客户还可以直接通过浏览器收看。客户希望点播一个节目时,QuickTime Player或插件将向QTSS发送请求,指明要点播的节目名。如果该节目存在,QTSS将向客户发送相应的视频流。当客户希望收看现场直播(或实时广播)时,它首先从QTSS获得关于当前频道的编码格式、地址等相关信息,然后再接受该频道的媒体流。对于那些希望在Internet上实时流化视频或音频信息的用户,QTSS服务器将是一个很好的选择,通过它可实现多项任务,例如:创建一个24小时在线的Internet广播电台;现场实况转播:如公司会议、体育节目等;创建远程学习站点:如能够点播视频和演讲;图1是一个利用QTSS服务器建立的现场直播场景。2 Darwin流化服务器介绍 Darwin Streaming Server(简称DSS)是QuickTime Streaming Server开放式源代码的版本,同时支持FreeBSD、Linux、Solaris、Windows NT和Windows 2000等多个*作系统,是当前所有同类产品中支持平台最多的一个。DSS的源代码和相关文档可从以下站点获得: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)。这四个标准是开发所有流式媒体产品都必须掌握的,因此在对相关代码进行分析和二次开发之前,希望读者了解上述四种协议的基本思想,上述协议样本可从以下网站获得: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 OSMutex1 public:2 OSMutex(); /构造函数3 OSMutex(); /析构函数4 inline void Lock(); /加锁5 inline void Unlock(); /解锁6 inline Bool16 TryLock(); /异步锁,无论是否成功立即返回7 private:8 #ifdef _Win32_9 CRITICAL_SECTION fMutex; /临界区10 DWORD fHolder; /拥有临界区的线程id11 UInt32 fHolderCount; /进入临界区线程数/其他略在Windows平台上,OSMutex类是通过临界区(CRITICAL_SECTION)来实现的,第10行定义了临界区变量fMutex。类实例化时构造函数调用InitializeCriticalSection(&fMutex)初始化临界区变量,对应的在析构函数中调用DeleteCriticalSection(&fMutex)清除。Lock()函数用于对互斥量加锁,它调用私有方法RecursiveLock实现:void OSMutex:RecursiveLock()/ 当前线程已经拥有互斥量,只需增加引用计数1 if (OSThread:GetCurrentThreadID() = fHolder)2 3 fHolderCount+; /增加引用计数4 return;5 6 #ifdef _Win32_7 :EnterCriticalSection(&fMutex); /申请进入临界区8 #else9 (void)pthread_mutex_lock(&fMutex);10 #endif11 Assert(fHolder = 0);12 fHolder = OSThread:GetCurrentThreadID(); /更新临界区拥有者标志13 fHolderCount+;14 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 OSCond1 public:2 OSCond(); /构造函数3 OSCond(); /析构函数4 inline void Signal(); /传信函数5 inline void Wait(OSMutex* inMutex, SInt32 inTimeoutInMilSecs = 0);/等待传信函数6 inline void Broadcast(); /广播传信函数7 private:8 #ifdef _Win32_9 HANDLE fCondition; /事件句柄10 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()函数1 #ifdef _Win32_2 UInt32 waitCount = fWaitCount; /等待传信的用户数3 for (UInt32 x = 0; x fRunning = true),当Entry()函数运行完后再设为非运行状态;在运行过程中,用户可以通过StopAndWaitForThread()、join()、Detach()以及ThrowStopRequest()等函数改变线程其他状态变量。3.1.3 OSHashTable/OSQueue/OSHeap/OSRef ClassDSS定义了几个通用的较为复杂的数据结构,它们都以类的方式封装。这些数据结构不但贯穿于DSS的所有源代码,而且由于其封装的十分好,读者可以在看懂源代码的基础上很容易的将它们从DSS的工程中抽取出来,构建自己的基础类库,为将来的开发工作打下良好的基础。另外,对这些基础数据结构源代码的研究将提高我们对于面向对象技术的掌握和领会。最主要的数据结构有四种:哈希表(OSHashTable)、队列(OSQueue)、堆(OSHeap)和对象引用表(OSRef)。前三种是我们在编程中大量使用的数据结构,而对象引用表则是类似于COM/DCOM组件编程中IUNKOWN接口功能的数据结构,它首先为每个对象建立了一个字符串形式的ID,以便于通过这个ID找到对象(类似于QueryInteface);另外OSRef类还为每个对象实例建立了引用计数,只有一个对象不再被任何人引用,才可能被释放(类似于AddRef和Release)。鉴于这几个类在结构上有相似之处,下面我们将分析OSHashTable的源代码,以便能够帮助读者更好的理解其他几个类。OSHashTable的代码如下:templateclass OSHashTable /*提示:OSHashTable被设计成为一个类模版,两个输入参数分别为:class T:实际的对象类;class K:用于为class T计算哈希表键值的功能类。*/1 public:2 OSHashTable( UInt32 size ) /构造函数,入参是哈希表中对象的最大个数3 4 fHashTable = new ( T*size ); /申请分配size个哈希对象class T的空间5 Assert( fHashTable );6 memset( fHashTable, 0, sizeof(T*) * size ); /初始化7 fSize = size;/*下面的代码决定用哪种方式为哈希表的键值计算索引;如果哈希表的大小不是2的幂,只好采用对fSize求余的方法;否则可以直接用掩码的方式,这种方式相对速度更快*/8 fMask = fSize - 1;9 if (fMask & fSize) != 0) /fSize不是2的幂10 fMask = 0;11 fNumEntries = 0; /当前对象数12 13 OSHashTable() /析构函数14 15 delete fHashTable; /释放空间16 /下面介绍向哈希表中添加一个class T对象的源代码17 void Add( T* entry ) 18 Assert( entry-fNextHashEntry = NULL );/*利用功能类class K,计算class T对象的哈希键值,其计算方法由用户在class K中定义*/19 K key( entry );20 UInt32 theIndex = ComputeIndex( key.GetHashKey() );/利用键值计算索引21 entry-fNextHashEntry = fHashTable theIndex ; /在新加对象中存储索引值22 fHashTable theIndex = entry; /将该对象插入到索引指定的位置23 fNumEntries+; /24 /下面介绍从哈希表中删除一个class T对象的源代码25 void Remove( T* entry )26 /首先从哈希表中找到待删除的对象/1、计算哈希键值和其对应的对象索引27 key( entry );28 UInt32 theIndex = ComputeIndex( key.GetHashKey() );29 T* elem = fHashTable theIndex ;30 T* last = NULL;/*2、通过对象索引查找对象,如果不是要找的对象,接着找下一个,直到找到为止。这是因为,存放的时候就是按照这种模式计算索引的。*/31 while (elem & elem != entry) 32 last = elem;33 elem = elem-fNextHashEntry;34 /找到该对象,将其删除35 if ( elem )36 37 Assert(elem);38 if (last)39 last-fNextHashEntry = elem-fNextHashEntry;40 else /elem在头部41 fHashTable theIndex = elem-fNextHashEntry;42 elem-fNextHashEntry = NULL;43 fNumEntries-;44 45 /下面介绍从哈希表中查找一个class T对象的方法46 T* Map( K* key ) /入参为哈希键值47 48 UInt32 theIndex = ComputeIndex( key-GetHashKey() ); /计算索引49 T* elem = fHashTable theIndex ; /找到索引对应的对象50 while (elem) 51 K elemKey( elem );52 if (elemKey =*key) /检查是否找对53 break;54 elem = elem-fNextHashEntry; /如果不是,继续找下一个55 56 return elem;57 /以下略以上介绍了哈希表的构造以及三种基本*作:添加、删除和查询。另外,DSS还定义了OSHashTableIter类用于枚举OSHashTable中的class T对象;其中主要的*作有First和Next等,限于篇幅,此处就不再详述。3.2 Tasks类因为服务器从整体上采用了异步的运行模式,这就需要一种用于事件通信的机制。举例来说:一个RTSP连接对应的Socket端口监测到网络上有数据到达,此时必须有一个模块(或代码)被通知(notify)去处理这些数据。为此,DSS定义了Task及其相关类作为实现这一通信机制的核心。在Task.h/cpp文件中,定义了三个主要的类,分别是:任务线程池类(TaskThreadPool Class)、任务线程类(TaskThread Class)以及任务类(Task Class)。每个Task对象有两个主要的方法:Signal和Run。当服务器希望发送一个事件给某个Task对象时,就会调用Signal()方法;而Run()方法是在Task对象获得处理该事件的时间片后运行的,服务器中的大部分工作都是在不同Task对象的Run()函数中进行的。每个Task对象的目标就是利用很小的且不会阻塞的时间片完成服务器指定某个工作。任务线程类是上文介绍的OSThread类的一个子类,代表专门用于运行任务类的一个线程。在每个任务线程对象内部都有一个OSQueue_Blocking类型的任务队列,存储该线程需要执行的任务。后面的分析可以看到,服务器调用一个任务的Signal函数,实际上就是将该任务加入到某个任务线程类的任务队列中去。另外,为了统一管理这些任务线程,DSS还开发了任务线程池类,该类负责生成、删除以及维护内部的任务线程列表。图4描述了任务类的运行。下面我们首先分析TaskThread类,该类的定义如下:class TaskThread : public OSThread /OSThread的子类 /提示:所有的Task对象都将在TaskThread中运行1 public:2 TaskThread() : OSThread(), fTaskThreadPoolElem(this) /构造函数3 virtual TaskThread() this-StopAndWaitForThread(); /析构函数4 private:5 virtual void Entry(); /从OSThread重载的执行函数,仍然能够被子类重载6 Task* WaitForTask(); /检测是否有该执行的任务7 OSQueueElem fTaskThreadPoolElem; /对应的线程池对象8 OSHeap fHeap; /纪录任务运行时间的堆,用于WaitForTask函数/*关键数据结构:任务队列;在Task的Signal函数中直接调用fTaskQueue对象的EnQueue函数将自己加入任务队列*/9 OSQueue_Blocking fTaskQueue;/此处略作为OSThread的子类,TaskThread重载了Entry函数,一旦TaskThread的对象被实例化,便运行该函数。Entry()函数的主要任务就是调用WaitForTask()函数监测任务队列,如果发现新任务,就在规定时间内执行;否则,就被阻塞。下面我们简要分析Entry()函数的流程:void TaskThread:Entry()1 Task* theTask = NULL; /空任务2 while (true) /线程循环执行3 /监测是否有需要执行的任务,如果有就返回该任务;否则阻塞;4 theTask = this-WaitForTask();5 Assert(theTask != NULL);6 Bool16 doneProcessingEvent = false; /尚未处理事件7 while (!doneProcessingEvent)8 9 theTask-fUseThisThread = NULL; / 对任务的调度独立于线程10 SInt64 theTimeout = 0; /Task中Run函数的返回值,重要/核心部分:运行任务,根据返回值判断任务进度11 if (theTask-fWriteLock)12 /如果任务中有写锁,需要使用写互斥量,否则可能造成死锁13 OSMutexWriteLocker mutexLocker(&TaskThreadPool:sMutexRW);14 theTimeout = theTask-Run(); /运行任务,得到返回值15 theTask-fWriteLock = false;16 17 else18 /使用读互斥量19 OSMutexReadLocker mutexLocker(&TaskThreadPool:sMutexRW);20 theTimeout = theTask-Run(); /运行任务,得到返回值21 22 /监测Task中Run()函数的返回值,共有三种情况23 /1、返回负数,表明任务已经完全结束24 if (theTimeout fEvents);34 if (doneProcessingEvent)35 theTask = NULL;36 /3、返回正数,表明任务希望在等待theTimeout时间后再次执行37 else38 /*将该任务加入到Heap中,并且纪录它希望等待的时间。Entry()函数将通过waitfortask()函数进行检测,如果等待的时间到了,就再次运行该任务*/39 theTask-fTimerHeapElem.SetValue(OS:Milliseconds() + theTimeout);40 fHeap.Insert(&theTask-fTimerHeapElem);41 (void)atomic_or(&theTask-fEvents, Task:kIdleEvent);/设置Idle事件42 doneProcessingEvent = true;43 /此处略注意,如果Task的Run()函数返回值TimeOut为正数,意味着该任务是一个周期性的工作,例如发送数据的视频泵(pump),需要每隔一定时间就发出一定量的视频数据,直至整个节目结束。为此,在第3843行,将该任务加入到堆fHeap中去,并且标记该任务下次运行的时间为TimeOut毫秒之后。将来通过调用WaitForTask()函数就能检测到该任务是否到达规定的运行时间,WaitForTask()函数的代码如下:Task* TaskThread:WaitForTask()1 while (true)2 /得到当前时间,该函数为静态函数,定义见OS.h3 SInt64 theCurrentTime = OS:Milliseconds();/*如果堆中有任务,且任务已经到执行时间,返回该任务。 PeekMin函数见OSHeap.h,窃听堆中第一个元素(但不取出)*/4 if (fHeap.PeekMin() != NULL) & (fHeap.PeekMin()-GetValue() GetEnclosingObject();/如果堆中有任务,但是尚未到执行时间,计算需要等待的时间6 SInt32 theTimeout = 0;7 if (fHeap.PeekMin() != NULL) /计算还需等待的时间8 theTimeout = fHeap.PeekMin()-GetValue() - theCurrentTime;9 Assert(theTimeout = 0);/等待theTimeout时间后从堆中取出任务返回10 OSQueueElem* theElem = fTaskQueue.DeQueueBlocking(this, theTimeout);11 if (theElem != NULL)12 return (Task*)theElem-GetEnclosingObject();13 上文曾经提到,Task对象内有两个方法:Signal和Run。Run函数是一个虚函数,由Task的子类重载,它的用法我们在分析TaskThread的Entry()函数和WaitForTask()函数中已经讨论了。而另一个Signal()函数也十分重要:服务器通过调用该函数将Task加入TaskThread,并且执行Run()函数。Signal()函数的核心部分如下:void Task:Signal(EventFlags events)/ fUseThisThread用于指定该任务运行的任务线程1 if (fUseThisThread != NULL) /存在指定任务线程/将该任务加入到指定任务线程的任务队列中2 fUseThisThread-fTaskQueue.EnQueue(&fTaskQueueElem);/不存在指定的任务线程,随机选择一个任务线程运行该任务3 else4 /从线程池中随机选择一个任务线程5 unsigned int theThread = atomic_add(&sThreadPicker, 1);6 theThread %= TaskThreadPool:sNumTaskThreads;/将该任务加入到上面选择的任务线程的任务队列中7 TaskThreadPool:sTaskThreadArraytheThread- fTaskQueue.EnQueue (&fTaskQueueElem);8 至此我们已经将DSS的线程和任务运行机制分析完了,这种由事件去触发任务的概念已经被集成到了DSS的各个子系统中。例如,在DSS中经常将一个Task对象和一个Socket对象关联在一起,当Socket对象收到事件(通过select()函数),相对应的Task对象就会被传信(通过Signal()函数);而包含着处理代码的Run()函数就将在某个任务线程中运行。因此,通过使用这些Task对象,我们就可以让所有连接都使用一个线程来处理,这也是DSS的缺省配置方法。3.3 Socket类作为一个典型的网络服务器,DSS源代码中的Socket编程部分是其精华之一。DSS定义了一系列Socket类用于屏蔽不同平台在TCP/UDP编程接口和使用方法上的差异。DSS中的Socket类一般都采用异步模式的(即非阻塞的),而且能够向对应的Task对象传信(Signal),这点我们在上一节介绍过。Socket类中具有代表性的类是:EventContext、EventThread、Socket、UDPSocket、TCPSocket以及TCPListenerSocket等等,它们之间的继承关系见图5。在eventcontext.h/.cpp文件中,定义了两个类:EventContext类和EventThread类。 Event Context提供了检测Unix式的文件描述符(Socket就是一种文件描述符)产生的事件(通常是EV_RE 或 EV_WR)的能力,同时还可以传信指定的任务。EventThread类是OSThread类的子类,它本身很简单,只是重载了OSThread的纯虚函数Entry(),用以监控所有的Socket端口是否有数据到来,其代码分析如下:void EventThread:Entry()/*该结构定义在ev.h中,记录Socket描述符和在该描述符上发生的事件*/1 struct eventreq theCurrentEvent;2 :memset( &theCurrentEvent, 0, sizeof(theCurrentEvent) ); /初始化该结构3 while (true)4 /首先监听Socket端口的事件5 int theErrno = EINTR;6 while (theErrno=EINTR)7 8 #if MACOSXEVENTQUEUE /Macos平台9 int theReturnValue = waitevent(&theCurrentEvent, NULL);10 #else /其他平台/*调用select_waitevent函数监听所有的Socket端口,直到有事件发生为止*/11 int theReturnValue = select_waitevent(&theCurrentEvent, NULL);12 #endif/有事件发生,唤醒相应的Socket端口13 if (theCurrentEvent.er_data != NULL)14 /通过事件中的标识找到相应的对象参考指针15 StrPtrLen idStr(char*)&theCurrentEvent.er_data, sizeof(theCurrentEvent.er_data);16 OSRef* ref = fRefTable.Resolve(&idStr);17 if (ref != NULL)18 /通过参考指针得到EventContext对象19 EventContext* theContext = (EventContext*)ref-GetObject();/利用EventContext对象的ProcessEvent方法传信对应的Task20 theContext-ProcessEvent(theCurrentEvent.er_eventbits);21 fRefTable.Release(ref); /减少引用计数22 /此处略上述代码有两点需要注意:首先在第11行,调用select_waitevent函数监听所有Socket端口的事件。该函数在Windows平台上是采用WSAAsyncSelect(异步选择)模型实现的。具体实现是:系统首先创建一个窗口类,该类专门用于接受消息;在每个Socket端口创建后,调用WSAsyncSelect函数,同时将上述窗口类的句柄作为参数传入;将来这些Socket端口有事件发生时,Windows就会自动将这些事件映射为标准的Windows消息发送给窗口类,此时select_waitevent函数通过检查消息就能够获得对应Socket端口发生的事件。对于Windows平台下Socket的异步编程技术细节请参阅Windows网络编程技术一书。另外,在第20行调用的EventContext对象的ProcessEvent函数实现上很简单,只有一行代码:fTask-Signal(Task:kReadEvent);其中fTask为该EventContext对象对应的Task对象;ProcessEvent函数向Task对象传信,以便及时处理刚刚发生的Socket事件。与EventThread对应的EventContext对象负责维护指定的描述符,其主要函数包括InitNonBlocking、CleanUp和RequestEvent等。其中InitNonBlocking函数调用Socket API ioctlsocket将用户指定的描述符设置为异步,CleanUp函数用于关闭该描述符;另外,用户通过RequestEvent函数申请对该描述符中某些事件的监听,如前所述,该函数内部调用了WSAsyncSelect来实现这一功能。Socket Class、UDPSocket Class和TCPSocketClass三个类都是EventContext的子类,它们封装了TCP和UDP的部分实现,同时扩展了EventContext中的事件,但都没有改变其运行机制,因此此处不再详述,留给读者自行分析。我们要为大家分析的是另外一个比较复杂的Socket类TCPListenerSocket类。TCPListenerSocket用于监听TCP端口,当一个新连接请求到达后,该类将赋予这个新连接一个Socket对象和一个Task对象的配对。首先分析TCPListenerSocket类的主要定义如下:class TCPListenerSocket : public TCPSocket, public IdleTask/*提示:该类从有两个基类,所以它既是一个事件监听者,同时也是一个任务Task。作为一个任务,给TCPListenerObject发送Kill事件就可以删除它*/1 public:2 TCPListenerSocket() : TCPSocket(NULL, Socket:kNonBlockingSocketType), IdleTask(), fAddr(0), fPort(0), fOutOfDescript
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 超密物质介电响应-洞察阐释
- 2024年度河北省护师类之儿科护理主管护师真题练习试卷B卷附答案
- 车辆事故责任认定与经济赔偿服务合同
- 采石场承包合同矿产资源管理及开发利用协议
- 民族特色餐饮连锁店加盟合同
- 汽车维修厂专用车位租赁及维修服务合同
- 文化创意车间厂房租赁与IP授权合作协议
- 车辆贷款风险预警合同范本
- 现代化工厂厂房租赁安全协议范本
- 创建绿色施工管理制度
- 2023年全国高考体育单招英语高频考点归纳总结(复习必背)
- 2023年郴州市发改系统政策业务知识抢答赛题库
- 上海市应届小学毕业班学生登记表2优质资料
- 公司竞业协议书
- GB/T 42061-2022医疗器械质量管理体系用于法规的要求
- 网上支付跨行清算系统报文交换标准
- 燃气安全继续教育考试题及答案
- 无人机驾驶员高级工职业资格考试题库(高频题汇总)
- 供应商大会品质报告课件
- 职工《劳动法》与《劳动合同法》知识培训课件
- 人民日报-计算机(电脑)常用快捷键400个(超级实用)
评论
0/150
提交评论