1、1 1第10章 面向对象技术10.1 面向对象概述面向对象概述10.2 MFC CAsyncSocket类网络编程类网络编程10.3 MFC CSocket类网络编程类网络编程10.4 多媒体通信编程多媒体通信编程小结小结2 2除了采用链接库的封装方法,还有一种更具有优势的封装方法面向对象类封装。面向对象的编程技术采用与传统面向过程的编程方法不同的思想,已经在单机的应用程序设计中得到了有效验证。同样,在网络编程中,也可以提高程序的复用性和可读性。本章首先从面向对象的封装、继承和多态入手,在此基础上介绍了两种MFC提供的两种套接字类,为了深入介绍面向对象网络编程的好处,将其应用于多媒体通信编程。
2、两种套接字类是对传统API函数的封装,将二者比较进行学习有益于对本章内容的掌握。3 3在传统的面向过程的程序设计中:程序=算法+数据结构,这里的算法实际上就是功能抽象。在面向过程的程序设计中,程序是模块化的,模块是分层次的,层与层之间是一个从上往下的调用关系,如图10-1。10.1 面向对象概述面向对象概述4 4图10-1 面向过程的程序组织5 5图10-2 面向对象的程序组织6 6然而,功能抽象是困难的,而且很难全面。一旦要解决的问题发生一点小变化,功能块就要重编,而一个功能块又被多个上层模块调用,它们的要求有的变了、有的没变,这就给重编带来极大的困难。具体的缺陷表现为:利用传统的面向过程的
3、程序开发,即使开发一个简单的Windows应用程序也需要对编程原理有很深刻的认识,需要手工编写冗长的代码;程序的出错率是随着代码长度的增加呈几何级数增长的,当程序长度逐渐膨胀时,调试程序也会变得越来越困难。7 7鉴于面向过程的程序设计存在的缺陷,20世纪90年代流行起来的面向对象的程序设计将面向过程的程序设计核心思想替换为:程序=对象+对象+对象,其中对象=算法+数据结构。这样,程序变为一个个封装对象的结合体,而对象由紧密结合在一起的算法和数据结构组成,对象中有数据和对数据的操作,具有完整的个体功能,如图10-2所示。类是用来定义对象的一种抽象数据类型,它是产生对象的模板。类可以认为是一种特殊
4、的结构体,基本上说C+对C语言的最重要的改进就是增加了“类”这种数据类型。8 810.1.1 封装封装封装即是将某种东西包装隐藏起来,让外界无法直接使用,只能通过某些特定的方式才能访问。封装是面向对象的基础,它将底层的元素组合起来形成新的高层次实体,实质上是一种信息隐藏技术。面向对象是以类为单位进行代码的封装。在前面面向过程的编程中,我们已经知道函数也是封装的一种形式。函数将所执行的细节行为封装在函数本身这个更大的实体中,被封装的元素隐藏了它们的实现细节(可以调用函数却不能访问它所执行的语句)。与之类似,类也是一个封装的实体,它代表若干成员的聚集,它们中的大多数同样隐藏了实现该类型的成员。9
5、9定义一个类时也就定义了一个类型,用这个类型可以定义一个对象(类的用户),计算机系统会为定义的对象分配存储空间。类由类成员组成,成员又由成员变量和成员函数组成,分别代表类的特性和行为。成员又划分为三种类型,即public、private、protected,这三种类型实现了对类封装的分层次的信息隐藏。public成员:该类型成员可以被使用该类型的所有代码访问。private成员:该类型成员只可被本类其他类成员或友元(friend)访问,子类不可继承和访问。10 10protected成员:该类型成员可被其他类成员或友元访问,也可被子类继承和访问。对象只能访问public成员。这三种成员类型的访
6、问关系,可以用下面的程序进行说明。class Aprivate:int a;public:int b;11 11 void f1()a=0;b=1;c=2;protected:int c;class B:public Apublic:int d;void f2()c=0;12 12根据成员public、private、protected的性质,对A、B对象的访问A ca、ca.b=1、ca.f1()、B cb、cb.b=2、cb.d=3、cb.f1()、cb.f2()均是合法的。对于ca.a、ca.c、cb.a、cb.c的访问都不合法。13 1310.1.2 继承继承通俗地讲,后代具有源自祖先
7、的某些特点就叫继承。如果只有封装,那么非面向对象语言的结构体也能做到,继承则把代码复用提升到了一个新高度。面向对象的继承包含两层含义,即继续和发展。不继续父类的特性则失去继承的本义,子类不发展出新的特性则失去存在意义(沿用父类即可)。继承是一种树状的层次关系,子类在继承祖先类的成员变量和成员函数的同时,也可以定义自己的成员变量和成员函数。14 14子类继承父类有三种方法,即公共、私有和受保护继承(为了区别public、private和protected,这里使用中文),这三种继承方法会使得父类中的成员信息隐藏特性发生变化,具体如下:公有继承:基类的成员保持自己的访问级别,基类的public成员
8、为派生类的public成员,基类的protected成员为派生类的protected成员。私有继承:基类的所有成员在派生类中变为private成员。受保护继承:基类的public和protected成员在派生类中变为protected成员。15 15继承的本质就是实现代码重用,因而继承机制能够缩短软件的开发周期,加快编程速度。16 1610.1.3 多态多态为了增加类的继承灵活性,面向对象技术提出了类的多态(动态绑定)特性,简称多态性。多态性是指发出的同样消息被不同类型的对象接收有可能导致完全不同的行为。利用多态性技术可以用同一个函数名的函数,实现完全不同的功能,这与客观世界的事实完全相符。多
9、态是面向对象系统的重要特性,对于同一个消息可以根据发送消息的对象的不同采用多种不同的处理方式。多态性主要是依靠virtual(虚函数)实现的,C+中在成员函数的最前面加上关键字virtual就可以将该函数设置为虚函数,在子类中可以使用相同的函数名对其进行重新定义。17 17对于在父类中无法被具体定义的虚函数,可以将其定义为纯虚函数(即函数定义后再加上“=0”的说明)。纯虚函数一般不在(通常也无法在)父类中具体定义,仅在子类中具体定义,这种定义很有用,例如:“动物”这个类可以派生出“昆虫”、“哺乳动物”、“鸟”等子类,但是在动物类本身中无法具体定义“吃”这个函数,因此需将eat()定义为纯虚函数
10、,如下:class animal public:virtual void eat()=0;18 18有纯虚函数的类不能直接生成对象,即上述的类的“animal a;”就不合法。需注意的是,虚函数只在public和protected中定义,针对父类中可能在子类中修改的函数才适合。构造函数因其里面要插入对虚函数表指针的初始化语句,因此也不能声明为虚函数。通常,尽可能地把函数声明为虚函数是一个好的编程习惯,这样做增加了子类设计的自由度。在MFC(参见2.8.2节)中,微软利用这个类库践行了面向对象网络编程的优越性,10.2节和10.3节将分别进行介绍。鉴于篇幅原因,关于更多的面向对象的知识请参阅其他
11、书籍,这里仅作概念性介绍。19 19如前所述,MFC封装了大约有100多个类,直接与套接字编程有关的主要是两个类,即CAsyncSocket和CSocket(还有一个从CSocket派生的CCeSocket类,主要用于WinCE平台,这里不记在内),从本节开始将分别进行介绍。10.2 MFC CAsyncSocket类网类网络编程络编程202021 2110.2.1 CAsyncSocket类类CAsyncSocket派生自CObject类(结构如图10-3所示),利用它可以方便地处理有关网络行为的通知消息。CAsyncSocket封装了低层的WinSock API,它在原有的WinSock函
12、数的基础上提供了一些面向对象的特性,如连接、接收事件。与其他面向对象的C+插口类不同,CAsyncSocket类所提供的唯一的抽象就是将与套接字相联系的Windows消息以回调函数的形式表示,可以说CAsyncSocket类是用于异步WinSock调用的一个包容类。2222缺省情况下,使用该类创建的Socket是非阻塞的Socket,所有操作都会立即返回,如果没有得到结果,返回WSAEWOULDBLOCK,表示是一个阻塞操作。CAsyncSocket有一个特殊的成员变量m_hSocket,用来保存其对应的Socket句柄。与前面介绍的基于WinSock API的编程接口相比,CAsyncSoc
13、ket类的编程接口除了封装了低层的WinSock API,还增加了很多智能化的设计,最重要的就是对I/O模型(参见6.6.2节)的封装,从而把程序员从复杂的I/O管理中解放出来。CAsyncSocket类是通过封装了一个CSocketWnd窗口实现这一功能的,封装是在CAsyncSocket的Create()函数中实现的,函数原型如下:2323bool CAsyncSocket:Create(long lEvent)m_hSocket=socket(PF_INET,SOCK_STREAM,0);/创建Socket本身 CSocketWnd*pSockWnd=new CSocketWnd;/创建
14、响应事件的CSocketWnd窗口 pSockWnd-Create();/将Socket事件和窗口用WSAAsyncSelect关联 WSAAsyncSelect(m_hSocket,pSockWnd-m_hWnd,WM_SOCKET_NOTIFY,lEvent);2424可以看出,该函数除了创建了一个SOCKET以外还创建了一个CSocketWnd窗口对象,并使用WSAAsyncSelect()将这个SOCKET与该窗口对象关联,达到让该窗口对象处理来自Socket的事件(消息)的目的。在MFC框架的支持下,一旦CSocketWnd收到Socket事件后,会回调CAsyncSocket的Do
15、CallBack成员函数,进而调用CAsyncSocket的成员函数OnReceive()、OnSend()、OnAccept()、OnConnect()等虚函数,进行I/O处理。所以使用CAsyncSocket进行套接字编程时,只需要在它的派生类里的这些虚函数里添加用户自己的发送和接收代码即可,而无需再进行I/O模型的管理,这大大简化了编程过程。CAsyncSocket:DoCallBack的原型如下:2525static void PASCAL CAsyncSocket:DoCallBack(WPARAM wParam,LPARAM lParam)CAsyncSocket*pSocket;
16、pSocket.Attach(SOCKET)wParam);/wParam就是触发这个事件的Socket的句柄 int nErrorCode=WSAGETSELECTERROR(lParam);/lParam是错误码与事件码的合成 switch(WSAGETSELECTEVENT(lParam)2626 case FD_READ:pSocket-OnReceive(nErrorCode);break;case FD_WRITE:pSocket-OnSend(nErrorCode);break;case FD_OOB:pSocket-OnOutOfBandData(nErrorCode);bre
17、ak;case FD_ACCEPT:2727 pSocket-OnAccept(nErrorCode);break;case FD_CONNECT:pSocket-OnConnect(nErrorCode);break;case FD_CLOSE:pSocket-OnClose(nErrorCode);break;2828那么CSocketWnd与CAsyncSocket是如何建立联系的呢?是通过CSocketWnd类中定义的消息与函数的映射实现的。该消息映射为BEGIN_MESSAGE_MAP(CSocketWnd,CWnd)ON_MESSAGE(WM_SOCKET_NOTIFY,OnSoc
18、ketNotify)END_MESSAGE_MAP()也就是一旦发生WM_SOCKET_NOTIFY事件(Socket事件),就会调用CSocketWnd:2929OnSocketNotify函数。CSocketWnd:OnSocketNotify函数原型如下:LRESULT CSocketWnd:OnSocketNotify(WPARAM wParam,LPARAM lParam)CAsyncSocket:DoCallBack(wParam,lParam);/收到Socket事件消息后,回调CAsyncSocket的DoCallBack()函数 return 0;实际上就是调用了CAsync
19、Socket:DoCallBack()函数。303010.2.2 类成员类成员下面对CAsyncSocket的类成员函数进行简要介绍,具体定义与使用方法可参看MSDN。1构造函数构造函数CAsyncSocket用来在创建CAsyncSocket对象时初始化对象,为对象成员变量赋初始值,与new运算符一起使用在创建对象的语句中。CAsyncSocke有两个构造函数CasyncSocket()和Create(),前者用于构造一个CAsyncSocket对象,后者用于创建一个socket。31 312属性函数属性函数为了增加程序设计的灵活性和可控性,CAsyncSocket还提供了属性函数和重载函数
20、,大大增加了程序员在编写网络程序时的自由度。CAsyncSocket类的属性函数包括Attach()、Detach()、FromHandle()、GetLastError()、GetPeerName()、GetSockName()、GetSockOpt()和SetSockOpt()函数,它们可以获得或改变对象的属性,为基于CAsyncSocket类的网络编程提供了网络控制与监测工具。32323操作函数操作函数CAsyncSocket操作函数进行网络相关建立的具体操作,与前面介绍的WinSock API的对应函数非常象,实际上就是对这些API函数的封装。操作函数包括Accept()、Bind()
21、、Close()、Connect()、IOCtl()、Listen()、Receive()、ReceiveFrom()、Send()、SendTo()、ShutDown()。33334重载函数重载函数CAsyncSocket类的重载函数能够向相应程序通告套接字事件的发生,可以通过重载设计处理相应的网络通信事件,这些函数都是虚函数。重载函数包括OnAccept()、OnClose()、OnConnect()、OnOutOfBandData()、OnReceive()、OnSend()。后续的网络处理主要就是使用这些函数。CAsyncSocket类本身只有一个成员变量,即m_hSocket,用于指
22、示连接到CAsyncSocket对象的句柄。从前面的叙述可以看出,CAsyncSocket直接封装了低层的WinSock API,同时提供了丰富的成员,简化和丰富了WinSock编程。343410.2.3 编程步骤编程步骤直接使用CAsyncSocket进行网络编程的具体使用方法可以概括为以下五个步骤:(1)在堆或者栈中构造一个CAsyncSocket对象,例如:CAsyncSocket sock或者CAsyncSocket*pSock=new CAsyncSocket;(2)调用Create()创建socket,如:使用缺省参数创建一个面向连接的socket,sock.Create()指定参
23、数创建一个使用数据报的socket,本地端口为30,程序代码如下:pSocket.Create(30,SOCK_DGRM);3535(3)如果是客户程序,使用Connect()连接到远地;如果是服务程序,使用Listen()监听远地的连接请求。当收到连接请求后,调用Accept()接受该请求。在接收连接请求后,就可以执行例如确认之类的操作。(4)使用成员函数进行网络I/O,完成与其他程序的通信和操作。(5)销毁CAsyncSocket,析构函数调用,Close()成员函数关闭socket。在MFC网络编程中,通常不直接使用CAsyncSocket类而是会对CAsyncSocket派生出一个新类
24、,并在该类中增加用户自己的一些网络功能(成员)。3636CSocket类是MFC提供的另外一个套接字类,在CAsyncSocket基础上又增加了一些新特性。10.3 MFC CSocket类网络编程类网络编程373710.3.1 CSocket类类CSocket类建立在CAsyncSocket类的基础上,是CAsyncSocket类的派生类,派生结构如图10-3所示。CSocket类的对象的用法与CAsyncSocket类的对象非常类似,且提供了以下三点改进。1成员成员CSocket主要增加或重载了构造函数Csocket()、Create();属性函数IsBlocking()、FromHand
25、le()、Attach();操作函数CancelBlockingCall();重载函数OnMessagePending()。38382同步化同步化CSocket对CAsyncSocket进行了同步化改造,成为一个典型的同步阻塞Socket封装类。同步的方法很简单,以CSocket的操作函数Connect()为例进行说明。CSocket在Connect()被阻塞时,不是在OnConnect()、OnReceive()这些事件终端函数里去等待,而是马上进入一个消息循环,从当前线程的消息队列里取关心的消息。如果取到了WM_PAINT消息,则刷新窗口;如果取到的是Socket发来的消息,则根据Sock
26、et是否有操作错误码,再调用相应的回调函数(OnConnect()等)。Connect()函数的原型如下:3939BOOL CSocket:Connect(.)if(!CAsyncSocket:Connect(.)if(WSAGetLastError()=WSAEWOULDBLOCK)/由于被阻塞不能立即完成,Socket/返回这个错误 /进入消息循环,从线程消息队列里查看FD_CONNECT消息,直到收到该消息而认为4040/连接成功 while(PumpMessages(FD_CONNECT);PumpMessages()函数的原型如下:BOOL CSocket:PumpMessages(
27、UINT uEvent)CWinThread*pThread=AfxGetThread();41 41 while(bBlocking)/bBlocking仅仅是一个标志,看用户是否取消对Connect()的调用 MSG msg;if(PeekMessage(&msg,WM_SOCKET_NOTIFY)if(msg.message=WM_SOCKET_NOTIFY&WSAGETSELECTEVENT(msg.lParam)=uStopFlag)4242 CAsyncSocket:DoCallBack(msg.wParam,msg.lParam);return TRUE;else 4343 On
28、MessagePending();/处理消息队列里的其他消息 pThread-OnIdle(-1);OnMessagePending实现了前面描述的在阻塞时,从当前线程的消息队列里取关心的消息的功能。BOOL CSocket:OnMessagePending()4444 MSG msg;if(PeekMessage(&msg,NULL,WM_PAINT,WM_PAINT,PM_REMOVE)/这里仅关心WM_PAINT消息,以处理阻塞期间的主窗口重画 :DispatchMessage(&msg);return FALSE;return FALSE;4545其他的CSocket函数,诸如Send
29、()、Receive()、Accept()都会在收到WSAEWOULDBLOCK错误被阻塞时,进入PumpMessages()消息循环,这样一个原本异步的CAsyncSocket就变成同步的了(实际上CSocket的阻塞不是建立在阻塞的socket基础上,而是在非阻塞socket上实现的阻塞操作),从而更加节省资源、更高效。46463串行化串行化CSocket另一个重要特点就是实现了数据读/写的串行化,提供了可以同CArchive和CSocketfile这两个类协同工作的接口,三者之间的关系如图10-4所示。其中,CSocketfile类是一种特殊的文件对象,常用它来通过套接字进行数据的传输,
30、它所提供的接口同普通文件类的接口基本上是一致的。而CArchive类是一个存档类,它是将用户的数据保存到永久性存储对象的一种技术。这种复杂的协同工作关系的目的是为了使用户从套接字细节的管理中脱身出来。4747用户在使用CSocket类时,不必像使用CAsyncSocket类时不得不面对一大堆繁琐的工作,而是像读/写一般文件一样直接进行数据读取就可以了。这样程序员只需要简单地创建套接字、文档和归档,然后通过存储或输入归档以发送或接收数据。4848图10-4 CSocket、CArchive和CSocketfile三者之间关系4949当然,CSocket类也可以单独使用,不过这样做的话,CSock
31、et的优势就会被大打折扣。下面将在介绍CSocketFile和CArchive的基础上,再具体介绍基于CSocket类的编程。505010.3.2 CSocketFile类 CSocketFile类继承自CFile类,用于通过WinSock向网络发送和接收数据。为了深刻掌握该类的功能与作用,在介绍CSocketFile类之前首先简单介绍一下CFile类。Cfile类是微软基础文件类的基本类。它可以直接提供无缓存、二进制文件的输入/输出服务,也可以通过它的基类直接支持对文本文件和内存文件的读写操作。CFile类与其基类的继承关系保障了程序能够通过多态的CFile接口操作所有的文件对象。51 51
32、Cfile类有着一整套的进行磁盘文件操作的成员函数,包括Open()、Close()、Read()、Write()、Seek()、GetPosition()、Duplicate()、GetLength()、Rename()、GetStatus()等,分别能够完成对文件的打开、关闭、读/写、设定/获得读取指针位置、复制、获取长度、重命名、获取文件状态等操作。Cfile类提供的静态成员函数允许程序员在不打开文件的情况下了解文件的状态,使得对文件的管理变得非常方便。特别是,CFile 对象还可以使用 CArchive 类对象进行MFC对象的序列化操作。5252在了解了Cfile类的基本含义和用法后,
33、就不难了解CSocketFile类了。CsocketFile类操作的对象不是文件(或内存内容),而是与CSocket类对象关联的网络I/O,在进行网络编程的时候,可以简单地理解为:引入CSocketFile类的作用实际上就是把网络I/O操作虚拟成为对文件的读写。这就省去了许多数据接收和发送时的繁琐操作!但是需要注意的是:虽然CsocketFile类继承自CFile类,但是CSocketFile对象不支持CFile的一些成员函数,如Seek()、GetLength()、SetLength()、LockRange()、GetPosition()等函数,调用会出错。5353想要利用上述CSocket
34、File对象便利地进行网路通信操作,必须将CSocketFile对象连接到一个CSocket对象上,执行该类的下面成员函数来实现。CSocketFile(CSocket*pSocket,BOOL bArchiveCompatible=TRUE);参数pSocket为指向关联的CSocket对象指针,参数bArchiveCompatible为是否要使文件对象与归档一起使用(允许不与归档一起使用),缺省值为TURE。5454为了进一步简化基于MFC的串行化编程,还可以把这个连接Csocket对象的CSocketFile对象再连接到CArchive对象上。555510.3.3 CArchive类类C
35、Archive类没有基类,它的对象能够对一个缓冲区进行管理。通过提供一个安全缓冲机制,可以以一个CFile对象(可以是磁盘文件,也可以是内存文件)为基础,通过“”操作符完成对文件的二进制流的操作。CArchive可以在对象被删除时,确保数据还能被存储介质永久保存;也可以从永久存储中装载对象,在内存中重新构造它们,这一使数据永久保留的过程就叫作“串行化”。CArchive对象将数据序列化到存档时,存档积累数据,直到其缓冲区被填满为止。5656然后,存档将其缓冲区写入 CArchive 对象指向的 CFile 对象。同样,当从存档中读取数据时,存档会将数据从文件读取到它的缓冲区,然后从缓冲区读取到
36、反序列化的对象。这种缓冲减少了物理读取硬盘的次数,从而提高了应用程序的性能。更重要的是,CArchive类对象允许以一个永久二进制(通常为磁盘存储)的形式保存一个对象的复杂网络,这一点对WinSock网络编程非常有用。根据CArchive对象能够对一个缓冲区进行管理的特点,再加上上述CSocketFile是一种特殊的Cfile,其对象完全能够被CArchive对象所管理,同时考虑到CSocket可以提供阻塞操作,因而通过三者配合完全可以像读/写文件一样读/写Socket数据。5757将CArchive对象连接到一个CSocketFile对象上,可通过执行该类的构造函数操作实现。m_arIn=n
37、ew CArchive(m_file,CArchive:load);/建立数据流入的CArchive对象与CSocketFile对象关联m_arOut=new CArchive(m_file,CArchive:store);/建立数据流出的CArchive对象与CSocketFile对象关联这里,m_arIn、m_arOut为CArchive对象,m_file是CSocketFile对象。5858CArchive类拥有众多的成员函数,如Carchive()、Read()、Write()、SetStoreParams()、SetObjectSchema()分别完成对象的构造、数据的读/写、参数的
38、设定等功能,但在网络编程中最常用的是“”操作和Flush函数,分别完成对数据的写和读。二者都可进行多种数据的操作,如果出现异常,将控制权交给异常处理程序处理。Flush()函数的原型为void Flush();throw(CFileException);5959该函数用于确定是否所有的数据已经从CArchive对象传入文件中,如果出现异常,将控制权交给异常处理程序处理。另外,CArchive类还有一个非常重要的成员函数IsBufferEmpty()。当CSocketFile()独立使用时,因为数据是分多个消息多次送达的,所以进行Read操作时可能出现无限等待。可能出现的问题是:没有读取到指定长
39、度的数据并不表示数据读取完毕,从而导致程序阻塞。对于这种情况,可以让CSocketFile和CArchive配合使用,但仅仅读取到数据就返回。至于数据是否读取完毕,就需要使用CArchive的IsBufferEmpty()函数来判断是否缓存里存在数据,只要存在数据就可以进行读操作了。IsBufferEmpty()函数的原型为6060BOOL IsBufferEmpty()const;如果数据为空,函数返回非0值,否则为0。在利用CArchive类对象编程时应当注意,给定的 CArchive 对象要么存储数据(即写入数据或将数据序列化),要么加载数据(即读取数据或将数据反序列化),但决不能同时进
40、行。CArchive 对象的寿命只限于将对象写入文件或从文件读取对象的一次传递。因此,需要在一个相关上进行双工通信,就需要两个连续创建的CArchive 对象,分别用于将数据序列化到文件和从文件反序列化数据。61 6110.3.4 编程步骤编程步骤CSocket类提供了比CAsyncSocket类更高级的抽象,它继承了CAsyncSocket类的许多封装了Windows插口API的成员函数,而且管理了通信的大多数方面,包括大部分本应使用原始API或者CAsyncSocket类进行处理的工作,因而使用更加容易。除了上面提到的它使用MFC序列化协议完成套接字数据处理,与CAsyncSocket类不
41、同,CSocket类的网络I/O可以设定为是阻塞的(缺省情况下是非阻塞),即它在完成任务之后才返回。这在网络编程中是极其有用的。6262利用CSocket类进行编程,要重点了解它的阻塞特点,以及CSocket类、CArchive类和CSocketfile类之间的关系,在编程过程中尤其要注意上述三个类对象的使用顺序。使用CSocket进行网络编程的具体使用方法可以概括为以下八个步骤。(1)构造服务器和服务器套接字对象;(2)调用Create()函数创建套接字;(3)套接字创建完毕后,服务器调用Listen()函数开始监听客户机的连接请求,而客户机可以调用Connetct()函数向服务器发出连接请
42、求;6363(4)当服务器监听到客户机有连接请求时,创建一个新的套接字,再调用Accept()成员函数以接收客户机的连接请求;(5)服务器和客户机的套接字对象分别创建一个与其联系的CSocketFile对象;(6)服务器和客户机的套接字对象分别创建两个与CSocketfile相关联的CArchive对象,以便进行数据的发送和接收;(7)使用CArchive对象在客户机和服务器套接字之间传送数据;6464(8)任务完成后,销毁CArchive、CSocketFile和CSocket对象。由于CArchive对象只能单向传递数据,因此在实际使用中必须定义两个CArchive对象,一个用于发送,另一
43、个用于数据的接收。在以上工作步骤中,第(1)(4)步与CAsyncSocket类的编程步骤完全相同,需要重点区别的在于第(5)(8)步。6565为了演示MFC套接字的编程,本节介绍一个多媒体网络通信编程的实现。10.4 多媒体通信编程多媒体通信编程666610.4.1 多媒体网络传输技术多媒体网络传输技术在网络上传输音/视频数据除了网络本身的传输数据以外,还需要使用压缩编码技术,压缩编码技术可以说是计算机处理、传输音/视频数据的前提。通常,视频信号数字化后数据带宽很高,达20Mb/s以上,因此计算机很难对其进行保存处理、网络传输。原始的单个音频数据虽然有时不是很大,但是由于音频数据使用非常频繁
44、,往往是多路、并发的,因此不进行任何压缩处理,也不适合直接在网络上进行传输。采用压缩编码技术以后通常可以使视频数据带宽降到110Mb/s,音频数据的压缩效率更高,这样就可以将音/视频信号在网络上传输了。67671压缩编码标准压缩编码标准传统的压缩编码是建立在香农(Shannon)信息论基础上的。香农信息论以经典的集合论为基础,用统计概率模型来描述信源,从中可以发现,描述信源的数据量总是大于信源本身这一事实,可以推断数据冗余客观存在。因此,通过去除数据的冗余信息就可以达到压缩的目的。压缩技术的目标就是尽可能多地将数据中的冗余信息去掉(去除数据之间的相关性)。压缩编码的发展历程实际上是以香农信息论
45、为出发点,一个不断完善的过程。6868由于多媒体数据有极强的相关性,也就是说有大量的冗余信息,因此非常适合压缩编码技术的运用。视频中的冗余信息可分为空域冗余信息和时域冗余信息,针对这些冗余信息的特征可以采用帧内图像数据压缩技术、帧间图像数据压缩技术和熵编码压缩技术。数字音频信息存在音频信息自身的相关性以及人耳对音频信息的听觉冗余度,因此可以采用波形编码、参数编码以及混合编码等三类技术。目前,很多实用、有效的音/视频压缩编码标准已经被广泛的运用到各个领域,常见的有:69691)MPEG系列由ISO国际标准组织机构下属的MPEG(运动图像专家组)开发,视频编码方面主要是MPEG-1(VCD)、MP
46、EG-2(DVD)、MPEG-4(如DIVX、XVID等)、MPEG-4 AVC;音频编码方面主要是MPEG Audio Layer 1/2、MPEG Audio Layer 3(MP3)、MPEG-2 AAC、MPEG-4 AAC等。2)H.26X系列由ITU国际电视讯联盟主导,侧重视频数据的网络传输,包括H.261、H.262、H.263、H.263+、H.263+、H.264等。70703)其他联合视频工作组(JVT,Joint Video Team)的新一代数字视频压缩标准;监控行业中分辨率标准有SQCIF、QCIF、CIF、4CIF,等等。71 712流媒体技术流媒体技术所谓流媒体,
47、是指采用流式传输的方式在Internet播放的媒体格式。流媒体又叫流式媒体,它是指视频提供者用一个视频传送服务器把节目当成数据包发出,传送到网络上。用户通过解压设备对这些数据进行解压后,视频就会像发送前那样显示出来。与这个过程的一系列相关的包称为“流”。流媒体实际指的是一种新的媒体传送方式,而非一种新的媒体。7272流式传输的实现需要缓存。因为Internet以包传输为基础进行断续的异步传输,对一个实时A/V源或存储的A/V文件,在传输中它们要被分解为许多包,由于网络是动态变化的,各个包选择的路由可能不尽相同,故到达客户端的时间延迟也就不等,甚至先发的数据包还有可能后到。为此,可以使用缓存系统
48、来弥补延迟和抖动的影响,并保证数据包的顺序正确,从而使媒体数据能连续输出,而不会因为网络暂时拥塞而使播放出现停顿。7373通常高速缓存所需容量并不大,因为高速缓存使用环形链表结构来存储数据:通过丢弃已经播放的内容,流可以重新利用空出的高速缓存空间来缓存后续尚未播放的内容。流式传输的实现需要合适的传输协议。由于TCP需要较多的开销,故不太适合传输实时数据。在流式传输的实现方案中,一般采用HTTP/TCP来传输控制信息,而用RTP/UDP来传输实时声音数据。流式传输的过程一般是这样的:用户选择某一流媒体服务后,Web浏览器与Web服务器之间使用HTTP/TCP交换控制信息,7474以便把需要传输的
49、实时数据从原始信息中检流媒体制作检索出来;然后客户机上的Web浏览器启动A/V Helper程序,用HTTP从Web服务器检索相关参数并对Helper程序进行初始化。这些参数可能包括目录信息、A/V数据的编码类型或与A/V检索相关的服务器地址7575流媒体实现需要三部分软/硬件产品,具体如下:(1)编码器:它由计算机、视频采集卡和流媒体编码软件组成。流媒体采集卡负责将音/视频信息源输入计算机,供编码软件处理;编码软件负责将流媒体采集卡传送过来的数字音/视频信号压缩成流媒体格式。如果做直播,它还负责实时地将压缩好的流媒体信号上传给流媒体服务器。(2)服务器:由流媒体软件系统服务器和一台硬件服务器
50、组成。这部分负责管理、存储、分发编码器传上来的流媒体节目。7676(3)终端播放器(解码器):这部分由流媒体系统的播放软件和一台普通PC组成,用来播放用户想要收看的流媒体服务器上的视频节目。77773DirectShowDirectShow是微软公司在ActiveMovie和Video for Windows的基础上推出的新一代基于COM(Component Object Model)的流媒体处理的开发包,与DirectX开发包一起发布。目前,DirectX的最新版本为11。DirectShow为多媒体流的捕捉和回放提供了强有力的支持。运用DirectShow,可以很方便地从支持WDM驱动模型