?? mfc教程_ 14socket類的設計和實現.htm
字號:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0042)http://www.vczx.com/tutorial/mfc/mfc14.php -->
<HTML><HEAD><TITLE>MFC教程_ SOCKET類的設計和實現</TITLE>
<META http-equiv=Content-Type content="text/html; charset=gb2312">
<META content="MSHTML 6.00.2800.1106" name=GENERATOR></HEAD>
<BODY bgColor=#ffffff>
<OL start=14>
<P align=justify>
<LI><A name=_Toc452641014></A><A name=_Toc457299154></A><B>SOCKET類的設計和實現</B>
<P></P>
<OL>
<P align=justify>
<LI><B><A name=_Toc452641015></A><A name=_Toc457299155></A>WinSock基本知識</B>
<P></P>
<P align=justify>這里不打算系統地介紹socket或者WinSock的知識。首先介紹WinSock
API函數,講解阻塞/非阻塞的概念;然后介紹socket的使用。</P>
<OL>
<P align=justify>
<LI><A name=_Toc452641016></A><A name=_Toc457299156></A><B>WinSock API</B>
<P></P>
<P
align=justify>Socket接口是網絡編程(通常是TCP/IP協議,也可以是其他協議)的API。最早的Socket接口是Berkeley接口,在Unxi操作系統中實現。WinSock也是一個基于Socket模型的API,在Microsoft
Windows操作系統類中使用。它在Berkeley接口函數的基礎之上,還增加了基于消息驅動機制的Windows擴展函數。Winscok1.1只支持TCP/IP網絡,WinSock2.0增加了對更多協議的支持。這里,討論TCP/IP網絡上的API。</P>
<P align=justify>Socket接口包括三類函數:</P>
<P align=justify>第一類是WinSock API包含的Berkeley
socket函數。這類函數分兩部分。第一部分是用于網絡I/O的函數,如</P>
<P
align=justify>accept、Closesocket、connect、recv、recvfrom、Select、Send、Sendto</P>
<P align=justify>另一部分是不涉及網絡I/O、在本地端完成的函數,如</P>
<P
align=justify>bind、getpeername、getsockname、getsocketopt、htonl、htons、inet_addr、inet_nton</P>
<P
align=justify>ioctlsocket、listen、ntohl、ntohs、setsocketopt、shutdow、socket等</P>
<P align=justify>第二類是檢索有關域名、通信服務和協議等Internet信息的數據庫函數,如</P>
<P
align=justify>gethostbyaddr、gethostbyname、gethostname、getprotolbyname</P>
<P align=justify>getprotolbynumber、getserverbyname、getservbyport。</P>
<P align=justify>第三類是Berkekley
socket例程的Windows專用的擴展函數,如gethostbyname對應的WSAAsynGetHostByName(其他數據庫函數除了gethostname都有異步版本),select對應的WSAAsynSelect,判斷是否阻塞的函數WSAIsBlocking,得到上一次Windsock
API錯誤信息的WSAGetLastError,等等。</P>
<P
align=justify>從另外一個角度,這些函數又可以分為兩類,一是阻塞函數,一是非阻塞函數。所謂阻塞函數,是指其完成指定的任務之前不允許程序調用另一個函數,在Windows下還會阻塞本線程消息的發送。所謂非阻塞函數,是指操作啟動之后,如果可以立即得到結果就返回結果,否則返回表示結果需要等待的錯誤信息,不等待任務完成函數就返回。</P>
<P align=justify>首先,異步函數是非阻塞函數;</P>
<P align=justify>其次,獲取遠地信息的數據庫函數是阻塞函數(因此,WinSock提供了其異步版本);</P>
<P align=justify>在Berkeley socket函數部分中,不涉及網絡I/O、本地端工作的函數是非阻塞函數;</P>
<P align=justify>在Berkeley
socket函數部分中,網絡I/O的函數是可阻塞函數,也就是它們可以阻塞執行,也可以不阻塞執行。這些函數都使用了一個socket,如果它們使用的socket是阻塞的,則這些函數是阻塞函數;如果它們使用的socket是非阻塞的,則這些函數是非阻塞函數。</P>
<P
align=justify>創建一個socket時,可以指定它是否阻塞。在缺省情況下,Berkerley的Socket函數和WinSock都創建“阻塞”的socket。阻塞socket通過使用select函數或者WSAAsynSelect函數在指定操作下變成非阻塞的。WSAAsyncSelect函數原型如下。</P>
<P align=justify>int WSAAsyncSelect( </P>
<P align=justify>SOCKET s, </P>
<P align=justify>HWND hWnd, </P>
<P align=justify>u_int wMsg, </P>
<P align=justify>long lEvent </P>
<P align=justify>);</P>
<P
align=justify>其中,參數1指定了要操作的socket句柄;參數2指定了一個窗口句柄;參數3指定了一個消息,參數4指定了網絡事件,可以是多個事件的組合,如:</P>
<P align=justify>FD_READ 準備讀</P>
<P align=justify>FD_WRITE 準備寫</P>
<P align=justify>FD_OOB 帶外數據到達</P>
<P align=justify>FD_ACCEPT 收到連接</P>
<P align=justify>FD_CONNECT 完成連接</P>
<P align=justify>FD_CLOSE 關閉socket。</P>
<P align=justify>用OR操作組合這些事件值,如FD_READ|FD_WRITE</P>
<P align=justify>WSAAsyncSelect函數表示對socket
s監測lEvent指定的網絡事件,如果有事件發生,則給窗口hWnd發送消息wMsg。</P>
<P align=justify>假定應用程序的一個socket
s指定了監測FD_READ事件,則在FD_READ事件上變成非阻塞的。當read函數被調用時,不管是否讀到數據都馬上返回,如果返回一個錯誤信息表示還在等待,則在等待的數據到達后,消息wMsg發送給窗口hWnd,應用程序處理該消息讀取網絡數據。</P>
<P
align=justify>對于異步函數的調用,以類似的過程最終得到結果數據。以gethostbyname的異步版本的使用為例進行說明。該函數原型如下:</P>
<P align=justify>HANDLE WSAAsyncGetHostByName( </P>
<P align=justify>HWND hWnd, </P>
<P align=justify>u_int wMsg, </P>
<P align=justify>const char FAR *name, </P>
<P align=justify>char FAR *buf, </P>
<P align=justify>int buflen </P>
<P align=justify>);</P>
<P
align=justify>在調用WSAAsyncGetHostByName啟動操作時,不僅指定主機名字name,還指定了一個窗口句柄hWnd,一個消息ID
wMsg,一個緩沖區及其長度。如果不能立即得到主機地址,則返回一個錯誤信息表示還在等待。當要的數據到達時,WinSock
DLL給窗口hWnd發送消息wMsg告知得到了主機地址,窗口過程從指定的緩沖區buf得到主機地址。</P>
<P
align=justify>使用異步函數或者非阻塞的socket,主要是為了不阻塞本線程的執行。在多進程或者多線程的情況下,可以使用兩個線程通過同步手段來完成異步函數或者非阻塞函數的功能。</P>
<P align=justify></P>
<LI><A name=_Toc452641017></A><A name=_Toc457299157></A><B>Socket的使用</B>
<P></P></LI></OL></LI></OL></LI></OL>
<P align=justify>WinSock以DLL的形式提供,在調用任何WinSock
API之前,必須調用函數WSAStartup進行初始化,最后,調用函數WSACleanUp作清理工作。</P>
<P
align=justify>MFC使用函數AfxSocketInit包裝了函數WSAStartup,在WinSock應用程序的初始化函數IninInstance中調用AfxSocketInit進行初始化。程序不必調用WSACleanUp。</P>
<P align=justify></P>
<P
align=justify>Socket是網絡通信過程中端點的抽象表示。Socket在實現中以句柄的形式被創建,包含了進行網絡通信必須的五種信息:連接使用的協議,本地主機的IP地址,本地進程的協議端口,遠地主機的IP地址,遠地進程的協議端口。</P>
<P
align=justify>要使用socket,首先必須創建一個socket;然后,按要求配置socket;接著,按要求通過socket接收和發送數據;最后,程序關閉此socket。</P>
<UL>
<P align=justify>
<LI>為了創建socket,使用socket函數得到一個socket句柄:
<P></P></LI></UL>
<P align=justify>socket_handle = socket(protocol_family. Socket_type,
protocol);</P>
<P
align=justify>其中:protocol_family指定socket使用的協議,取值PF_INET,表示Internet(TCP/IP)協議族;Socket_type指socket面向連接或者使用數據報;第三個參數表示使用TCP或者UDP協議。</P>
<P
align=justify>當一個socket被創建時,WinSock將為一個內部結構分配內存,在此結構中保存此socket的信息,到此,socket連接使用的協議已經確定。</P>
<UL>
<P align=justify>
<LI>創建了socket之后,配置socket。
<P></P></LI></UL>
<P
align=justify>對于面向連接的客戶,WinSock自動保存本地IP地址和選擇協議端口,但是必須使用connect函數配置遠地IP地址和遠地協議端口:</P>
<P align=justify>result = connect(socket_handle, remote_socket_address,
address_length)</P>
<P
align=justify>remote_socket_address是一個指向特定socket結構的指針,該地址結構為socket保存了地址族、協議端口、網絡主機地址。</P>
<P align=justify>面向連接的服務器則使用bind指定本地信息,使用listen和accept獲取遠地信息。</P>
<P align=justify>使用數據報的客戶或者服務器使用bind給socket指定本地信息,在發送或者接收數據時指定遠地信息。</P>
<P align=justify>bind給socket指定一個本地IP地址和協議端口,如下:</P>
<P align=justify>result = bind( socket_hndle, local_socket_address,
address_length)</P>
<P align=justify>參數類型同connect。</P>
<P
align=justify>函數listen監聽bind指定的端口,如果有遠地客戶請求連接,使用accept接收請求,創建一個新的socket,并保存信息。</P>
<P align=justify>socket_new = accept(socket_listen, socket_address,
address_length)</P>
<UL>
<P align=justify>
<LI>在socket配置好之后,使用socket發送或者接收數據。
<P></P></LI></UL>
<P align=justify>面向連接的socket使用send發送數據,recv接收數據;</P>
<P align=justify>使用數據報的socket使用sendto發送數據,recvfrom接收數據。</P>
<OL>
<OL>
<P align=justify>
<LI><A name=_Toc452641018></A><A name=_Toc457299158></A><B>MFC對WinSockt
API的封裝</B>
<P></P>
<P align=justify>MFC提供了兩個類CAsyncSocket和CSocket來封裝WinSock
API,這給程序員提供了一個更簡單的網絡編程接口。</P>
<P align=justify>CAsyncSocket在較低層次上封裝了WinSock
API,缺省情況下,使用該類創建的socket是非阻塞的socket,所有操作都會立即返回,如果沒有得到結果,返回WSAEWOULDBLOCK,表示是一個阻塞操作。</P>
<P
align=justify>CSocket建立在CAsyncSocket的基礎上,是CAsyncSocket的派生類。也就是缺省情況下使用該類創建的socket是非阻塞的socket,但是CSocket的網絡I/O是阻塞的,它在完成任務之后才返回。CSocket的阻塞不是建立在“阻塞”socket的基礎上,而是在“非阻塞”socket上實現的阻塞操作,在阻塞期間,CSocket實現了本線程的消息循環,因此,雖然是阻塞操作,但是并不影響消息循環,即用戶仍然可以和程序交互。</P>
<OL>
<P align=justify>
<LI><A name=_Toc452641019></A><A
name=_Toc457299159></A><B>CAsyncSocket</B>
<P></P>
<P align=justify>CAsyncSocket封裝了低層的WinSock
API,其成員變量m_hSocket保存其對應的socket句柄。使用CAsyncSocket的方法如下:</P>
<P align=justify>首先,在堆或者棧中構造一個CAsyncSocket對象,例如:</P>
<P align=justify>CAsyncSocket sock;或者</P>
<P align=justify>CAsyncSocket *pSock = new CAsyncSocket;</P>
<P align=justify>其次,調用Create創建socket,例如:</P>
<P align=justify>使用缺省參數創建一個面向連接的socket</P>
<P align=justify>sock.Create() </P>
<P align=justify>指定參數參數創建一個使用數據報的socket,本地端口為30</P>
<P align=justify>pSocket.Create(30, SOCK_DGRM);</P>
<P align=justify>其三,如果是客戶程序,使用Connect連接到遠地;如果是服務程序,使用Listen監聽遠地的連接請求。</P>
<P align=justify>其四,使用成員函數進行網絡I/O。</P>
<P align=justify>最后,銷毀CAsyncSocket,析構函數調用Close成員函數關閉socket。</P>
<P align=justify></P>
<P align=justify>下面,分析CAsyncSocket的幾個函數,從中可以看到它是如何封裝低層的WinSock
API,簡化有關操作的;還可以看到它是如何實現非阻塞的socket和非阻塞操作。</P>
<P align=justify></P>
<LI><A name=_Toc452641020></A><A
name=_Toc457299160></A><B>socket對象的創建和捆綁</B>
<P></P>
<P align=justify>(1)Create函數</P>
<P
align=justify>首先,討論Create函數,分析socket句柄如何被創建并和CAsyncSocket對象關聯。Create的實現如下:</P>
<P align=justify>BOOL CAsyncSocket::Create(UINT nSocketPort, int
nSocketType,</P>
<P align=justify>long lEvent, LPCTSTR lpszSocketAddress)</P>
<P align=justify>{</P>
<P align=justify>if (Socket(nSocketType, lEvent))</P>
<P align=justify>{</P>
<P align=justify>if (Bind(nSocketPort,lpszSocketAddress))</P>
<P align=justify>return TRUE;</P>
<P align=justify>int nResult = GetLastError();</P>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -