一、引言
近年來,利用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 句柄??梢园慈缦聝煞N方法構造:
CAsyncSocket sock;
Sock.Create();
// 使用默認參數產生一個字節流套接字
或
CAsyncSocket*pSocket=new CAsyncSocket;
int nPort=27;
pSocket->Create(nPort, SOCK-DGRAM);
// 指定端口號產生一個數據報套接字
第一種方法在棧上產生一個CAsyncSocket
對象,而第二種方法在堆上產生CAsyncSocket 對象。第一種Create
成員函數用缺省參數產生一個字節流套接字,第二種Create
成員函數用指定的端口和地址產生一個數字報套接字。Create
的參數有:
?、俣丝?,UINT
類型。注意:如果是服務方,則使用一個眾所周知的端口供服務方連接;如果是客戶方,典型做法是接受默認參數,使套接字可以自主選擇一個可用端口;
?、趕ocket 類型。SOCK-STREAM(默認值)或SOCK-DGRAM;
?、踫ocket 地址。例如“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
步對于客戶方和服務方操作是不同的,其他步驟都相同。
?。?)構造一個CSocket 對象。
?。?)使用這個對象的Create 成員函數產生一個socket
句柄。在客戶方程序中,除非需要數據報套接字,Create
一般情況下應該使用默認參數。而對于服務方程序,必須在調用Create
時指定一個端口。注意:CArchive 不能與數據報(UDP)套接字一起工作,因此對于數據報套接字,CAsyncSocket
和CSocket 的使用方法是一樣的。
?。?)如果是客戶方套接字,則調用CAsyncSocket ∷Connect
與服務方套接字連接;如果是服務方套接字,則調用CAsyncSocket ∷Listen
開始監聽來自客戶方的連接請求,收到連接請求后,調用CAsyncSocket
∷Accept 接受請求,建立連接。注意:Accept
成員函數需要一個新的并且為空的CSocket
對象作為它的參數,解釋同上。
?。?)產生一個CSocketFile 對象,并把它與CSocket 對象關聯起來。
?。?)為接收和發送數據各產生一個CArchive 對象,把它們與CSocketFile
對象關聯起來。切記CArchive 是不能和數據報套接字一起工作的。
(6) 使用CArchive 對象在客戶與服務方傳送數據。(7)
通訊完畢后,銷毀CArchive、CSocketFile 和CSocket 對象。