Winsock是Windows下的網(wǎng)絡(luò)編程接口,它是由Unix下的BSD Socket發(fā)展而來(lái),是一個(gè)與網(wǎng)絡(luò)協(xié)議無(wú)關(guān)的編程接口。
Windows下網(wǎng)絡(luò)編程的規(guī)范-Windows Sockets是Windows下得到廣泛應(yīng)用的、開(kāi)放的、支持多種協(xié)議的網(wǎng)絡(luò)編程接口。從1991年的1.0版到1995年的2.0.8版,經(jīng)過(guò)不斷完善并在Intel、Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下,已成為Windows網(wǎng)絡(luò)編程的事實(shí)上的標(biāo)準(zhǔn)。
Windows Sockets規(guī)范以U.C. Berkeley大學(xué)BSD UNIX中流行的Socket接口為范例定義了一套Micosoft Windows下網(wǎng)絡(luò)編程接口。它不僅包含了人們所熟悉的Berkeley Socket風(fēng)格的庫(kù)函數(shù);也包含了一組針對(duì)Windows的擴(kuò)展庫(kù)函數(shù),以使程序員能充分地利用Windows消息驅(qū)動(dòng)機(jī)制進(jìn)行編程。Windows Sockets規(guī)范本意在于提供給應(yīng)用程序開(kāi)發(fā)者一套簡(jiǎn)單的API,并讓各家網(wǎng)絡(luò)軟件供應(yīng)商共同遵守。此外,在一個(gè)特定版本W(wǎng)indows的基礎(chǔ)上,Windows Sockets也定義了一個(gè)二進(jìn)制接口(ABI),以此來(lái)保證應(yīng)用Windows Sockets API的應(yīng)用程序能夠在任何網(wǎng)絡(luò)軟件供應(yīng)商的符合Windows Sockets協(xié)議的實(shí)現(xiàn)上工作。因此這份規(guī)范定義了應(yīng)用程序開(kāi)發(fā)者能夠使用,并且網(wǎng)絡(luò)軟件供應(yīng)商能夠?qū)崿F(xiàn)的一套庫(kù)函數(shù)調(diào)用和相關(guān)語(yǔ)義。遵守這套Windows Sockets規(guī)范的網(wǎng)絡(luò)軟件,我們稱之為Windows Sockets兼容的,而Windows Sockets兼容實(shí)現(xiàn)的提供者,我們稱之為Windows Sockets提供者。一個(gè)網(wǎng)絡(luò)軟件供應(yīng)商必須百分之百地實(shí)現(xiàn)Windows Sockets規(guī)范才能做到現(xiàn)Windows Sockets兼容。任何能夠與Windows Sockets兼容實(shí)現(xiàn)協(xié)同工作的應(yīng)用程序就被認(rèn)為是具有Windows Sockets接口。我們稱這種應(yīng)用程序?yàn)閃indows Sockets應(yīng)用程序。Windows Sockets規(guī)范定義并記錄了如何使用API與Internet協(xié)議族(IPS,通常我們指的是TCP/IP)連接,尤其要指出的是所有的Windows Sockets實(shí)現(xiàn)都支持流套接口和數(shù)據(jù)報(bào)套接口.應(yīng)用程序調(diào)用Windows Sockets的API實(shí)現(xiàn)相互之間的通訊。Windows Sockets又利用下層的網(wǎng)絡(luò)通訊協(xié)議功能和操作系統(tǒng)調(diào)用實(shí)現(xiàn)實(shí)際的通訊工作。它們之間的關(guān)系如圖
通信的基礎(chǔ)是套接口(Socket),一個(gè)套接口是通訊的一端。在這一端上你可以找到與其對(duì)應(yīng)的一個(gè)名字。一個(gè)正在被使用的套接口都有它的類型和與其相關(guān)的進(jìn)程。套接口存在于通訊域中。通訊域是為了處理一般的線程通過(guò)套接口通訊而引進(jìn)的一種抽象概念。套接口通常和同一個(gè)域中的套接口交換數(shù)據(jù)(數(shù)據(jù)交換也可能穿越域的界限,但這時(shí)一定要執(zhí)行某種解釋程序)。Windows Sockets規(guī)范支持單一的通訊域,即Internet域。各種進(jìn)程使用這個(gè)域互相之間用Internet協(xié)議族來(lái)進(jìn)行通訊(Windows Sockets 1.1以上的版本支持其他的域,例如Windows Sockets 2)。套接口可以根據(jù)通訊性質(zhì)分類;這種性質(zhì)對(duì)于用戶是可見(jiàn)的。應(yīng)用程序一般僅在同一類的套接口間通訊。不過(guò)只要底層的通訊協(xié)議允許,不同類型的套接口間也照樣可以通訊。用戶目前可以使用兩種套接口,即流套接口和數(shù)據(jù)報(bào)套接口。流套接口提供了雙向的,有序的,無(wú)重復(fù)并且無(wú)記錄邊界的數(shù)據(jù)流服務(wù)。數(shù)據(jù)報(bào)套接口支持雙向的數(shù)據(jù)流,但并不保證是可靠,有序,無(wú)重復(fù)的。也就是說(shuō),一個(gè)從數(shù)據(jù)報(bào)套接口接收信息的進(jìn)程有可能發(fā)現(xiàn)信息重復(fù)了,或者和發(fā)出時(shí)的順序不同。數(shù)據(jù)報(bào)套接口的一個(gè)重要特點(diǎn)是它保留了記錄邊界。對(duì)于這一特點(diǎn),數(shù)據(jù)報(bào)套接口采用了與現(xiàn)在許多包交換網(wǎng)絡(luò)(例如以太網(wǎng))非常類似的模型。
一個(gè)在建立分布式應(yīng)用時(shí)最常用的范例便是客戶機(jī)/服務(wù)器模型。在這種方案中客戶應(yīng)用程序向服務(wù)器程序請(qǐng)求服務(wù)。這種方式隱含了在建立客戶機(jī)/服務(wù)器間通訊時(shí)的非對(duì)稱性?蛻魴C(jī)/服務(wù)器模型工作時(shí)要求有一套為客戶機(jī)和服務(wù)器所共識(shí)的慣例來(lái)保證服務(wù)能夠被提供(或被接受)。這一套慣例包含了一套協(xié)議。它必須在通訊的兩頭都被實(shí)現(xiàn)。根據(jù)不同的實(shí)際情況,協(xié)議可能是對(duì)稱的或是非對(duì)稱的。在對(duì)稱的協(xié)議中,每一方都有可能扮演主從角色;在非對(duì)稱協(xié)議中,一方被不可改變地認(rèn)為是主機(jī),而另一方則是從機(jī)。一個(gè)對(duì)稱協(xié)議的例子是Internet中用于終端仿真的TELNET。而非對(duì)稱協(xié)議的例子是Internet中的FTP。無(wú)論具體的協(xié)議是對(duì)稱的或是非對(duì)稱的,當(dāng)服務(wù)被提供時(shí)必然存在"客戶進(jìn)程"和"服務(wù)進(jìn)程"。一個(gè)服務(wù)程序通常在一個(gè)眾所周知的地址監(jiān)聽(tīng)對(duì)服務(wù)的請(qǐng)求,也就是說(shuō),服務(wù)進(jìn)程一直處于休眠狀態(tài),直到一個(gè)客戶對(duì)這個(gè)服務(wù)的地址提出了連接請(qǐng)求。在這個(gè)時(shí)刻,服務(wù)程序被"驚醒"并且為客戶提供服務(wù)-對(duì)客戶的請(qǐng)求作出適當(dāng)?shù)姆磻?yīng)。這一請(qǐng)求/相應(yīng)的過(guò)程可以簡(jiǎn)單的用圖表示。雖然基于連接的服務(wù)是設(shè)計(jì)客戶機(jī)/服務(wù)器應(yīng)用程序時(shí)的標(biāo)準(zhǔn),但有些服務(wù)也是可以通過(guò)數(shù)據(jù)報(bào)套接口提供的。
數(shù)據(jù)報(bào)套接口可以用來(lái)向許多系統(tǒng)支持的網(wǎng)絡(luò)發(fā)送廣播數(shù)據(jù)包。要實(shí)現(xiàn)這種功能,網(wǎng)絡(luò)本身必須支持廣播功能,因?yàn)橄到y(tǒng)軟件并不提供對(duì)廣播功能的任何模擬。廣播信息將會(huì)給網(wǎng)絡(luò)造成極重的負(fù)擔(dān),因?yàn)樗鼈円缶W(wǎng)絡(luò)上的每臺(tái)主機(jī)都為它們服務(wù),所以發(fā)送廣播數(shù)據(jù)包的能力被限制于那些用顯式標(biāo)記了允許廣播的套接口中。廣播通常是為了如下兩個(gè)原因而使用的:1. 一個(gè)應(yīng)用程序希望在本地網(wǎng)絡(luò)中找到一個(gè)資源,而應(yīng)用程序?qū)υ撡Y源的地址又沒(méi)有任何先驗(yàn)的知識(shí)。2. 一些重要的功能,例如路由要求把它們的信息發(fā)送給所有可以找到的鄰機(jī)。被廣播信息的目的地址取決于這一信息將在何種網(wǎng)絡(luò)上廣播。Internet域中支持一個(gè)速記地址用于廣播-INADDR_BROADCAST。由于使用廣播以前必須捆綁一個(gè)數(shù)據(jù)報(bào)套接口,所以所有收到的廣播消息都帶有發(fā)送者的地址和端口。
Intel處理器的字節(jié)順序是和DEC VAX處理器的字節(jié)順序一致的。因此它與68000型處理器以及Internet的順序是不同的,所以用戶在使用時(shí)要特別小心以保證正確的順序。任何從Windows Sockets函數(shù)對(duì)IP地址和端口號(hào)的引用和傳送給Windows Sockets函數(shù)的IP地址和端口號(hào)均是按照網(wǎng)絡(luò)順序組織的,這也包括了sockaddr_in結(jié)構(gòu)這一數(shù)據(jù)類型中的IP地址域和端口域(但不包括sin_family域)。考慮到一個(gè)應(yīng)用程序通常用與"時(shí)間"服務(wù)對(duì)應(yīng)的端口來(lái)和服務(wù)器連接,而服務(wù)器提供某種機(jī)制來(lái)通知用戶使用另一端口。因此getservbyname()函數(shù)返回的端口號(hào)已經(jīng)是網(wǎng)絡(luò)順序了,可以直接用來(lái)組成一個(gè)地址,而不需要進(jìn)行轉(zhuǎn)換。然而如果用戶輸入一個(gè)數(shù),而且指定使用這一端口號(hào),應(yīng)用程序則必須在使用它建立地址以前,把它從主機(jī)順序轉(zhuǎn)換成網(wǎng)絡(luò)順序(使用htons()函數(shù))。相應(yīng)地,如果應(yīng)用程序希望顯示包含于某一地址中的端口號(hào)(例如從getpeername()函數(shù)中返回的),這一端口號(hào)就必須在被顯示前從網(wǎng)絡(luò)順序轉(zhuǎn)換到主機(jī)順序(使用ntohs()函數(shù))。由于Intel處理器和Internet的字節(jié)順序是不同的,上述的轉(zhuǎn)換是無(wú)法避免的,應(yīng)用程序的編寫(xiě)者應(yīng)該使用作為Windows Sockets API一部分的標(biāo)準(zhǔn)的轉(zhuǎn)換函數(shù),而不要使用自己的轉(zhuǎn)換函數(shù)代碼。因?yàn)閷?lái)的Windows Sockets實(shí)現(xiàn)有可能在主機(jī)字節(jié)順序與網(wǎng)絡(luò)字節(jié)順序相同的機(jī)器上運(yùn)行。因此只有使用標(biāo)準(zhǔn)的轉(zhuǎn)換函數(shù)的應(yīng)用程序是可移植的。
在MFC中MS為套接口提供了相應(yīng)的類CAsyncSocket和CSocket,CAsyncSocket提供基于異步通信的套接口封裝功能,CSocket則是由CAsyncSocket派生,提供更加高層次的功能,例如可以將套接口上發(fā)送和接收的數(shù)據(jù)和一個(gè)文件對(duì)象(CSocketFile)關(guān)聯(lián)起來(lái),通過(guò)讀寫(xiě)文件來(lái)達(dá)到發(fā)送和接收數(shù)據(jù)的目的,此外CSocket提供的通信為同步通信,數(shù)據(jù)未接收到或是未發(fā)送完之前調(diào)用不會(huì)返回。此外通過(guò)MFC類開(kāi)發(fā)者可以不考慮網(wǎng)絡(luò)字節(jié)順序和忽略掉更多的通信細(xì)節(jié)。
在一次網(wǎng)絡(luò)通信/連接中有以下幾個(gè)參數(shù)需要被設(shè)置:本地IP地址 - 本地端口號(hào) - 對(duì)方端口號(hào) - 對(duì)方IP地址。左邊兩部分稱為一個(gè)半關(guān)聯(lián),當(dāng)與右邊兩部分建立連接后就稱為一個(gè)全關(guān)聯(lián)。在這個(gè)全關(guān)聯(lián)的套接口上可以雙向的交換數(shù)據(jù)。如果是使用無(wú)連接的通信則只需要建立一個(gè)半關(guān)聯(lián),在發(fā)送和接收時(shí)指明另一半的參數(shù)就可以了,所以可以說(shuō)無(wú)連接的通信是將數(shù)據(jù)發(fā)送到另一臺(tái)主機(jī)的指定端口。此外不論是有連接還是無(wú)連接的通信都不需要雙方的端口號(hào)相同。
在創(chuàng)建CAsyncSocket對(duì)象時(shí)通過(guò)調(diào)用
BOOL CAsyncSocket::Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, LPCTSTR lpszSocketAddress = NULL )通過(guò)指明lEvent所包含的標(biāo)記來(lái)確定需要異步處理的事件,對(duì)于指明的相關(guān)事件的相關(guān)函數(shù)調(diào)用都不需要等待完成后才返回,函數(shù)會(huì)馬上返回然后在完成任務(wù)后發(fā)送事件通知,并利用重載以下成員函數(shù)來(lái)處理各種網(wǎng)絡(luò)事件: 標(biāo)記 事件 需要重載的函數(shù)
FD_READ 有數(shù)據(jù)到達(dá)時(shí)發(fā)生 void OnReceive( int nErrorCode );
FD_WRITE 有數(shù)據(jù)發(fā)送時(shí)產(chǎn)生 void OnSend( int nErrorCode );
FD_OOB 收到外帶數(shù)據(jù)時(shí)發(fā)生 void OnOutOfBandData( int nErrorCode );
FD_ACCEPT 作為服務(wù)端等待連接成功時(shí)發(fā)生 void OnAccept( int nErrorCode );
FD_CONNECT 作為客戶端連接成功時(shí)發(fā)生 void OnConnect( int nErrorCode );
FD_CLOSE 套接口關(guān)閉時(shí)發(fā)生 void OnClose( int nErrorCode );
我們看到重載的函數(shù)中都有一個(gè)參數(shù)nErrorCode,為零則表示正常完成,非零則表示錯(cuò)誤。通過(guò)int CAsyncSocket::GetLastError()可以得到錯(cuò)誤值。
下面我們看看套接口類所提供的一些功能,通過(guò)這些功能我們可以方便的建立網(wǎng)絡(luò)連接和發(fā)送數(shù)據(jù)。
BOOL CAsyncSocket::Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, LPCTSTR lpszSocketAddress = NULL );用于創(chuàng)建一個(gè)本地套接口,其中nSocketPort為使用的端口號(hào),為零則表示由系統(tǒng)自動(dòng)選擇,通常在客戶端都使用這個(gè)選擇。nSocketType為使用的協(xié)議族,SOCK_STREAM表明使用有連接的服務(wù),SOCK_DGRAM表明使用無(wú)連接的數(shù)據(jù)報(bào)服務(wù)。lpszSocketAddress為本地的IP地址,可以使用點(diǎn)分法表示如10.1.1.3。
BOOL CAsyncSocket::Bind( UINT nSocketPort, LPCTSTR lpszSocketAddress = NULL )作為等待連接方時(shí)產(chǎn)生一個(gè)網(wǎng)絡(luò)半關(guān)聯(lián),或者是使用UDP協(xié)議時(shí)產(chǎn)生一個(gè)網(wǎng)絡(luò)半關(guān)聯(lián)。
BOOL CAsyncSocket::Listen( int nConnectionBacklog = 5 )作為等待連接方時(shí)指明同時(shí)可以接受的連接數(shù),請(qǐng)注意不是總共可以接受的連接數(shù)。
BOOL CAsyncSocket::Accept( CAsyncSocket& rConnectedSocket, SOCKADDR* lpSockAddr = NULL, int* lpSockAddrLen = NULL )作為等待連接方將等待連接建立,當(dāng)連接建立后一個(gè)新的套接口將被創(chuàng)建,該套接口將會(huì)被用于通信。
BOOL CAsyncSocket::Connect( LPCTSTR lpszHostAddress, UINT nHostPort );作為連接方發(fā)起與等待連接方的連接,需要指明對(duì)方的IP地址和端口號(hào)。
void CAsyncSocket::Close( );關(guān)閉套接口。
int CAsyncSocket::Send( const void* lpBuf, int nBufLen, int nFlags = 0 )
int CAsyncSocket::Receive( void* lpBuf, int nBufLen, int nFlags = 0 );在建立連接后發(fā)送和接收數(shù)據(jù),nFlags為標(biāo)記位,雙方需要指明相同的標(biāo)記。
int CAsyncSocket::SendTo( const void* lpBuf, int nBufLen, UINT nHostPort, LPCTSTR lpszHostAddress = NULL, int nFlags = 0 )
int CAsyncSocket::ReceiveFrom( void* lpBuf, int nBufLen, CString& rSocketAddress, UINT& rSocketPort, int nFlags = 0 );對(duì)于無(wú)連接通信發(fā)送和接收數(shù)據(jù),需要指明對(duì)方的IP地址和端口號(hào),nFlags為標(biāo)記位,雙方需要指明相同的標(biāo)記。
我們可以看到大多數(shù)的函數(shù)都返回一個(gè)布爾值表明是否成功。如果發(fā)生錯(cuò)誤可以通過(guò)int CAsyncSocket::GetLastError()得到錯(cuò)誤值。
由于CSocket由CAsyncSocket派生所以擁有CAsyncSocket的所有功能,此外你可以通過(guò)BOOL CSocket::Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, LPCTSTR lpszSocketAddress = NULL )來(lái)創(chuàng)建套接口,這樣創(chuàng)建的套接口沒(méi)有辦法異步處理事件,所有的調(diào)用都必需完成后才會(huì)返回。
在上面的介紹中我們看到MFC提供的套接口類屏蔽了大多數(shù)的細(xì)節(jié),我們只需要做很少的工作就可以開(kāi)發(fā)出利用網(wǎng)絡(luò)進(jìn)行通信的軟件。