一、引言
近年来,利用Internet 进行网际间通讯, 在WWW 浏览、FTP、Gopher
这些常规服务,以及在网络电话、多媒体会议等这些对实时性要求严格的应用中成为研究的热点,而且已经是必需的了。Windows
环境下进行通讯程序设计的最基本方法是应用Windows Sockets
实现进程间的通讯,为此微软提供了大量基于Windows Sockets 的通讯API,如WinSock
API、WinInet API 和ISAPI,并一直致力于开发更快、更容易的通讯API,将其和MFC
集成在一起以使通讯编程越来越容易。
MFC 是VC
编程环境最重要的组成部分,它为用户提供了一大批预先定义的类和成员函数,封装了大量的Windows
API。同时VC 环境提供了与MFC 对象和代码一起工作的专用工具:AppStudio
源程序编辑器、AppWizard 和Class Wizard。应用MFC,可以使Windows
程序员用较少的时间和精力开发出复杂的通讯应用程序。
本文根据笔者自己在开发实时网络音频工具FreeTalk
过程中的一些经验,介绍Windows 环境下的常用API 和封装它们的MFC
类,重点介绍使用MFC 的CAsyncsocket 和CSocket
类编写网络通讯程序的方法,这两个类封装了WinSock API,并使他们更容易使用和更适应于MFC
编程环境。
二、Windows 环境下的通讯API 和相应的MFC 类
1. Windows Sockets(WinSock)API
Windows Sockets 定义了Windows
的网络编程接口,它基于加利福尼亚大学伯克利分校的伯克利Unix
Sockets。Windows Sockets 既包括BSD 风格的例程,还加入了Windows
的扩展部分,例如用于消息驱动的扩展函数。Windows Sockets
可以运行在许多网络协议之上,包括TCP/IP、XNS、DECNet、IPX/SPX 等。在Win32
环境下,Windows Sockets
提供线程安全。通过微软与标准组织的努力,为WinSock
定义了应用程序设计接口(WinSock API),可以非常方便地利用下层的网络协议(如TCP/IP)进行网络通讯。
通过提供两个类CAsyncSocket 和CSocket,MFC 支持使用WinSock API
通讯程序设计。MFC 把复杂的WinSock API
封装到类里,这使得编写应用程序更容易。CAsyncSocket 类逐个封装了WinSock
API,为高级网络程序员提供了更加有力而灵活的方法。这个类基于程序员了解网络通讯的假设,目的是为了在MFC
中使用WinSock,程序员有责任处理诸如阻塞、字节顺序和在Unicode 与MBCS
间转换字符的任务。为了给程序员提供更方便的接口以自动处理这些任务,MFC
给出了CSocket 类,这个类是由CAsyncSocket 类继承下来的,它提供了比CAsyncSocket
更高层的WinSock API 接口。Csocket 类和CsocketFile 类与Carchive
类一起合作来管理发送和接收的数据,这使管理数据收发更加便利。CSocket
对象提供阻塞模式,这对于Carchive
的同步操作是至关重要的。阻塞函数[ 比如Receive()、Send()、ReceiveFrom()、SendTo()
和Aclearcase/" target="_blank" >ccept()]
直到操作完成后才返回控制权,因此如果需要低层控制和高效率,就使用CasyncSock
类;如果需要方便,则可使用Csocket 类。2.Win32 Internet(WinInet)API
微软公布了一些使Internet 应用程序的设计比以前更快、更容易的API:WinInet
API,它提供了中高层通信函数,这使访问主要的Internet
协议变得相当容易。这些函数在程序员和WinSock
驱动之间提供了隔离层。有4 类WinInet API 函数:通用WinInet 函数、WinInet
文件传输协议(FTP)函数、WinInet Gopher 函数、WinInet
超文本传输协议(HTTP)函数。
事实上,MFC 把WinInet API 和ActiveX 技术封装进类,使Internet
编程更加容易,这些类包括CInternetSession、CInternetConnection、CInternetFile、CHttpConnection、CHttpFile、CGopherFile、CFtpConnection、CGopherConnection、CFileFind、CFtpFileFind、CGopherFileFind、CGopherLocator
和CInternetException。
3.Internet 服务器API(ISAPI)
微软的IIS 是惟一与Windows NT Server 操作系统紧密集成的WWW
服务器,它作为Internet/Intranet 服务器应用范围很广。IIS
允许扩展功能,这是通过ISAPI 来实现的,ISAPI 描述了与Internet
服务器之间的接口。用ISAPI
提供的工具,可建立高性能、高效率、满足商业安全及符合新的IIS
标准的Internet 服务器。同样,ISAPI 在MFC 中由典型的类所封装,包括CHttpFilter、CHttpFilterContext、CHttpServer、CHttpServerContext、Related
Classes 和CHtmlStream。
三、WinSock API 的MFC 封装类
一些网络应用程序( 如网络电话、多媒体会议工具)
实时性要求非常强,要求能够直接应用WinSock
发送和接收数据。这时设计者应该选择直接应用WinSock API 或者由MFC
封装的WinSock API。新开发的应用程序中,为了充分利用MFC
的优势,首选方案应当是MFC 中的CAsyncSocket 类和CSocket
类,这两个类完全封装了WinSock API,并提供更多的便利。本文介绍应用这两个类的编程模型,并引出相关的成员函数与一些概念的解释。
1.CAsyncSocket 类和CSocket 类简述
附图CAsyncSocket 类和CSocket 类的继承关系
CAsyncSocket 类和CSocket 类的继承关系由附图给出。CSocket 类是由CAsyncSocket
继承而来的,事实上,在MFC 中CAsyncSocket 逐个封装了WinSock API,每个CAsyncSocket
对象代表一个Windows Socket,使用CAsyncSocket
类要求程序员对网络编程较为熟悉。相比起来,CSocket 类是CAsyncSocket
的派生类,继承了它封装的WinSock API。一个CSocket 对象代表了一个比CAsyncSocket
对象更高层次的Windows Socket 抽象,CSocket 类与CSocketFile 类和CArchive
类一起工作来发送和接收数据,因此使用它更加容易。CSocket
对象提供阻塞模式,因为阻塞功能对于CArchive
的同步操作是至关重要的。在这里有必要对阻塞的概念作一解释:一个socket
可以处于“阻塞模式”或“非阻塞模式”,当一个套接字处于阻塞模式(即同步操作)时,它的阻塞函数直到操作完成才会返回控制权,之所以称为阻塞是因为此套接字的阻塞函数在完成操作返回之前什么也不能做。如果一个socket
处于非阻塞模式(即异步操作),则会被调用函数立即返回。在CAsyncSocket
类中可以用GetLastError 成员函数查询最后的错误,如果错误是WSAEWOULDBLOCK
则说明有阻塞,而CSocket 绝不会返回WSAEWOULDBLOCK,因为它自己管理阻塞。微软建议尽量使用非阻塞模式,通过网络事件的发生而通知应用程序进行相应的处理。但在CSocket
类中,为了利用CArchive
处理通讯中的许多问题和简化编程,它的一些成员函数总是具有阻塞性质的,这是因为CArchive
类需要同步的操作。在Win32
环境下,如果要使用具有阻塞性质的套接字,应该放在独立的工人线程中处理,利用多线程的方法使阻塞不至于干扰其他线程,也不会把CPU
时间浪费在阻塞上。多线程的方法既可以使程序员享受CSocket
带来的简化编程的便利,也不会影响用户界面对用户的反应。
2.CAsyncsocket 类编程模型
在一个MFC
应用程序中,要想轻松处理多个网络协议,而又不牺牲灵活性时,可以考虑使用CAsyncSocket
类,它的效率比CSocket 类要高。CAsyncSocket
类针对字节流型套接字的编程模型简述如下:
(1) 构造一个CAsyncSocket 对象,并用这个对象的Create
成员函数产生一个Socket 句柄。可以按如下两种方法构造:
CAsyncSocket sock;
Sock.Create();
// 使用默认参数产生一个字节流套接字
或
CAsyncSocket*pSocket=new CAsyncSocket;
int nPort=27;
pSocket->Create(nPort, SOCK-DGRAM);
// 指定端口号产生一个数据报套接字
第一种方法在栈上产生一个CAsyncSocket
对象,而第二种方法在堆上产生CAsyncSocket 对象。第一种Create
成员函数用缺省参数产生一个字节流套接字,第二种Create
成员函数用指定的端口和地址产生一个数字报套接字。Create
的参数有:
①端口,UINT
类型。注意:如果是服务方,则使用一个众所周知的端口供服务方连接;如果是客户方,典型做法是接受默认参数,使套接字可以自主选择一个可用端口;
②socket 类型。SOCK-STREAM(默认值)或SOCK-DGRAM;
③socket 地址。例如“ftp.gliet.edu.cn"或“202.193.64.33"。
(2) 如是客户方程序,用CAsyncSocket ∷Connect
成员函数连接到服务方;如是服务方程序,用CAsyncSocket ∷Listen
成员函数开始监听,一旦收到连接请求,则调用CAsyncSocket ∷Accept
成员函数开始接收。注意:CAsyncSocket ∷Accept
成员函数要用一个新的并且是空的CSocket
对象作为它的参数,这里所说的“空的”指的是这个新对象还没有调用Create
成员函数。
(3) 调用其他的CAsyncSocket 类成员函数进行通讯管理。
(4) 通讯结束后,销毁CAsyncSocket 对象。如果是在栈上产生的CAsyncSocket
对象,则对象超出定义的范围时自动被析构;如果是在堆上产生,也就是用了new
这个操作符,则必须使用delete 操作符销毁CAsyncSocket 对象。
3.CSocket 类编程模型
使用CSocket 对象涉及CArchive 和CSocketFile
类对象。以下介绍的针对字节流型套接字的操作步骤中,只有第3
步对于客户方和服务方操作是不同的,其他步骤都相同。
(1)构造一个CSocket 对象。
(2)使用这个对象的Create 成员函数产生一个socket
句柄。在客户方程序中,除非需要数据报套接字,Create
一般情况下应该使用默认参数。而对于服务方程序,必须在调用Create
时指定一个端口。注意:CArchive 不能与数据报(UDP)套接字一起工作,因此对于数据报套接字,CAsyncSocket
和CSocket 的使用方法是一样的。
(3)如果是客户方套接字,则调用CAsyncSocket ∷Connect
与服务方套接字连接;如果是服务方套接字,则调用CAsyncSocket ∷Listen
开始监听来自客户方的连接请求,收到连接请求后,调用CAsyncSocket
∷Accept 接受请求,建立连接。注意:Accept
成员函数需要一个新的并且为空的CSocket
对象作为它的参数,解释同上。
(4)产生一个CSocketFile 对象,并把它与CSocket 对象关联起来。
(5)为接收和发送数据各产生一个CArchive 对象,把它们与CSocketFile
对象关联起来。切记CArchive 是不能和数据报套接字一起工作的。
(6) 使用CArchive 对象在客户与服务方传送数据。(7)
通讯完毕后,销毁CArchive、CSocketFile 和CSocket 对象。