?? mfc教程_ 14socket類的設計和實現.htm
字號:
<P align=justify>Close();</P>
<P align=justify>WSASetLastError(nResult);</P>
<P align=justify>}</P>
<P align=justify>return FALSE;</P>
<P align=justify>}</P>
<P align=justify>其中:</P>
<P align=justify>參數1表示本socket的端口,缺省是0,如果要創建數據報的socket,則必須指定一個端口號。</P>
<P align=justify>參數2表示本socket的類型,缺省是SOCK_STREAM,表示面向連接類型。</P>
<P align=justify>參數3是屏蔽位,表示希望對本socket監測的事件,缺省是FD_READ | FD_WRITE | FD_OOB
| FD_ACCEPT | FD_CONNECT | FD_CLOSE。</P>
<P align=justify>參數4表示本socket的IP地址字符串,缺省是NULL。</P>
<P
align=justify>Create調用Socket函數創建一個socket,并把它捆綁在this所指對象上,監測指定的網絡事件。參數2和3被傳遞給Socket函數,如果希望創建數據報的socket,不要使用缺省參數,指定參數2是SOCK_DGRM。</P>
<P align=justify>如果上一步驟成功,則調用bind給新的socket分配端口和IP地址。</P>
<P align=justify>(2)Socket函數</P>
<P align=justify>接著,分析Socket函數,其實現如下:</P>
<P align=justify>BOOL CAsyncSocket::Socket(int nSocketType, long
lEvent,</P>
<P align=justify>int nProtocolType, int nAddressFormat)</P>
<P align=justify>{</P>
<P align=justify>ASSERT(m_hSocket == INVALID_SOCKET);</P>
<P align=justify></P>
<P align=justify>m_hSocket =
socket(nAddressFormat,nSocketType,nProtocolType);</P>
<P align=justify>if (m_hSocket != INVALID_SOCKET)</P>
<P align=justify>{</P>
<P align=justify>CAsyncSocket::AttachHandle(m_hSocket, this, FALSE);</P>
<P align=justify>return AsyncSelect(lEvent);</P>
<P align=justify>}</P>
<P align=justify>return FALSE;</P>
<P align=justify>}</P>
<P align=justify>其中:</P>
<P align=justify>參數1表示Socket類型,缺省值是SOCK_STREAM。</P>
<P align=justify>參數2表示希望監測的網絡事件,缺省值同Create,指定了全部事件。</P>
<P
align=justify>參數3表示使用的協議,缺省是0。實際上,SOCK_STREAM類型的socket使用TCP協議,SOCK_DGRM的socket則使用UDP協議。</P>
<P
align=justify>參數4表示地址族(地址格式),缺省值是PF_INET(等同于AF_INET)。對于TCP/IP來說,協議族和地址族是同值的。</P>
<P
align=justify>在socket沒有被創建之前,成員變量m_hSocket是一個無效的socket句柄。Socket函數把協議族、socket類型、使用的協議等信息傳遞給WinSock
API函數socket,創建一個socket。如果創建成功,則把它捆綁在this所指對象。</P>
<P align=justify>(3)捆綁(Attatch)</P>
<P
align=justify>捆綁過程類似于其他Windows對象,將在模塊線程狀態的WinSock映射中添加一對新的映射:this所指對象和新創建的socket對象的映射。</P>
<P
align=justify>另外,如果本模塊線程狀態的“socket窗口”沒有創建,則創建一個,該窗口在異步操作時用來接收WinSock的通知消息,窗口句柄保存到模塊線程狀態的m_hSocketWindow變量中。函數AsyncSelect將指定該窗口為網絡事件消息的接收窗口。</P>
<P align=justify>函數AttachHandle的實現在此不列舉了。</P>
<P align=justify>(4)指定要監測的網絡事件</P>
<P
align=justify>在捆綁完成之后,調用AsyncSelect指定新創建的socket將監測的網絡事件。AsyncSelect實現如下:</P>
<P align=justify>BOOL CAsyncSocket::AsyncSelect(long lEvent)</P>
<P align=justify>{</P>
<P align=justify>ASSERT(m_hSocket != INVALID_SOCKET);</P>
<P align=justify></P>
<P align=justify>_AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;</P>
<P align=justify>ASSERT(pState->m_hSocketWindow != NULL);</P>
<P align=justify></P>
<P align=justify>return WSAAsyncSelect(m_hSocket,
pState->m_hSocketWindow,</P>
<P align=justify>WM_SOCKET_NOTIFY, lEvent) != SOCKET_ERROR;</P>
<P align=justify>}</P>
<P align=justify>函數參數lEvent表示希望監視的網絡事件。</P>
<P align=justify>_ afxSockThreadState得到的是當前的模塊線程狀態,m_
hSocketWindow是本模塊在當前線程的“socket窗口”,指定監視m_hSocket的網絡事件,如指定事件發生,給窗口m_hSocketWindow發送WM_SOCKET_NOTIFY消息。</P>
<P
align=justify>被指定的網絡事件對應的網絡I/O將是異步操作,是非阻塞操作。例如:指定FR_READ導致Receive是一個異步操作,如果不能立即讀到數據,則返回一個錯誤WSAEWOULDBLOCK。在數據到達之后,WinSock通知窗口m_hSocketWindow,導致OnReceive被調用。</P>
<P
align=justify>指定FR_WRITE導致Send是一個異步操作,即使數據沒有送出也返回一個錯誤WSAEWOULDBLOCK。在數據可以發送之后,WinSock通知窗口m_hSocketWindow,導致OnSend被調用。</P>
<P
align=justify>指定FR_CONNECT導致Connect是一個異步操作,還沒有連接上就返回錯誤信息WSAEWOULDBLOCK,在連接完成之后,WinSock通知窗口m_hSocketWindow,導致OnConnect被調用。</P>
<P align=justify>對于其他網絡事件,就不一一解釋了。</P>
<P
align=justify>所以,使用CAsyncSocket時,如果使用Create缺省創建socket,則所有網絡I/O都是異步操作,進行有關網絡I/O時則必須覆蓋以下的相關函數:</P>
<P
align=justify>OnAccept、OnClose、OnConnect、OnOutOfBandData、OnReceive、OnSend。</P>
<P align=justify>(5)Bind函數</P>
<P
align=justify>經過上述過程,socket創建完畢,下面,調用Bind函數給m_hSocket指定本地端口和IP地址。Bind的實現如下:</P>
<P align=justify>BOOL CAsyncSocket::Bind(UINT nSocketPort, LPCTSTR
lpszSocketAddress)</P>
<P align=justify>{</P>
<P align=justify>USES_CONVERSION;</P>
<P align=justify></P>
<P align=justify>//使用WinSock的地址結構構造地址信息</P>
<P align=justify>SOCKADDR_IN sockAddr;</P>
<P align=justify>memset(&sockAddr,0,sizeof(sockAddr));</P>
<P align=justify></P>
<P align=justify>//得到地址參數的值</P>
<P align=justify>LPSTR lpszAscii = T2A((LPTSTR)lpszSocketAddress);</P>
<P align=justify>//指定是Internet地址類型</P>
<P align=justify>sockAddr.sin_family = AF_INET;</P>
<P align=justify></P>
<P align=justify>if (lpszAscii == NULL)</P>
<P align=justify>//沒有指定地址,則自動得到一個本地IP地址</P>
<P align=justify>//把32比特的數據從主機字節序轉換成網絡字節序</P>
<P align=justify>sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);</P>
<P align=justify>else</P>
<P align=justify>{</P>
<P align=justify>//得到地址</P>
<P align=justify>DWORD lResult = inet_addr(lpszAscii);</P>
<P align=justify>if (lResult == INADDR_NONE)</P>
<P align=justify>{</P>
<P align=justify>WSASetLastError(WSAEINVAL);</P>
<P align=justify>return FALSE;</P>
<P align=justify>}</P>
<P align=justify>sockAddr.sin_addr.s_addr = lResult;</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>//如果端口為0,則WinSock分配一個端口(1024—5000)</P>
<P align=justify>//把16比特的數據從主機字節序轉換成網絡字節序</P>
<P align=justify>sockAddr.sin_port = htons((u_short)nSocketPort);</P>
<P align=justify></P>
<P align=justify>//Bind調用WinSock API函數bind</P>
<P align=justify>return Bind((SOCKADDR*)&sockAddr,
sizeof(sockAddr));</P>
<P align=justify>}</P>
<P align=justify>其中:函數參數1指定了端口;參數2指定了一個包含本地地址的字符串,缺省是NULL。</P>
<P
align=justify>函數Bind首先使用結構SOCKADDR_IN構造地址信息。該結構的域sin_family表示地址格式(TCP/IP同協議族),賦值為AF_INET(Internet地址格式);域sin_port表示端口,如果參數1為0,則WinSock分配一個端口給它,范圍在1024和5000之間;域sin_addr是表示地址信息,它是一個聯合體,其中s_addr表示如下形式的字符串,“28.56.22.8”。如果參數沒有指定地址,則WinSock自動地得到本地IP地址(如果有幾個網卡,則使用其中一個的地址)。</P>
<P align=justify>(6)總結Create的過程</P>
<P
align=justify>首先,調用socket函數創建一個socket;然后把創建的socket對象映射到CAsyncSocket對象(捆綁在一起),指定本socket要通知的網絡事件,并創建一個“socket窗口”來接收網絡事件消息,最后,指定socket的本地信息。</P>
<P
align=justify>下一步,是使用成員函數Connect連接遠地主機,配置socket的遠地信息。函數Connect類似于Bind,把指定的遠地地址轉換成SOCKADDR_IN對象表示的地址信息(包括網絡字節序的轉換),然后調用WinSock函數Connect連接遠地主機,配置socket的遠地端口和遠地IP地址。</P>
<P align=justify></P>
<LI><A name=_Toc452641021></A><A name=_Toc457299161></A><B>異步網絡事件的處理</B>
<P></P></LI></OL>
<P
align=justify>當網絡事件發生時,“socket窗口”接收WM_SOCKET_NOTIFY消息,消息處理函數OnSocketNotify被調用。“socket窗口”的定義和消息處理是MFC實現的,這里不作詳細的討論。</P>
<P
align=justify>OnSocketNotify回調CAsyncSocket的成員函數DoCallBack,DoCallBack調用事件處理函數,如OnRead、OnWrite等。摘錄DoCallBack的一段代碼如下:</P>
<P align=justify>switch (WSAGETSELECTEVENT(lParam))</P>
<P align=justify>{</P>
<P align=justify>case FD_READ:</P>
<P align=justify>{</P>
<P align=justify>DWORD nBytes;</P>
<P align=justify>//得到可以一次讀取的字節數</P>
<P align=justify>pSocket->IOCtl(FIONREAD, &nBytes);</P>
<P align=justify>if (nBytes != 0)</P>
<P align=justify>pSocket->OnReceive(nErrorCode);</P>
<P align=justify>}</P>
<P align=justify>break;</P>
<P align=justify>case FD_WRITE:</P>
<P align=justify>pSocket->OnSend(nErrorCode);</P>
<P align=justify>break;</P>
<P align=justify>case FD_OOB:</P>
<P align=justify>pSocket->OnOutOfBandData(nErrorCode);</P>
<P align=justify>break;</P>
<P align=justify>case FD_ACCEPT:</P>
<P align=justify>pSocket->OnAccept(nErrorCode);</P>
<P align=justify>break;</P>
<P align=justify>case FD_CONNECT:</P>
<P align=justify>pSocket->OnConnect(nErrorCode);</P>
<P align=justify>break;</P>
<P align=justify>case FD_CLOSE:</P>
<P align=justify>pSocket->OnClose(nErrorCode);</P>
<P align=justify>break;</P>
<P align=justify></P>
<P
align=justify>lParam是WM_SOCKET_NOFITY的消息參數,OnSocketNotify傳遞給函數DoCallBack,表示通知事件。</P>
<P
align=justify>函數IOCtl是CAsyncSocket的成員函數,用來對socket的I/O進行控制。這里的使用表示本次調用Receive函數至多可以讀nBytes個字節。</P>
<P align=justify>從上面的討論可以看出,從創建socket到網絡I/O,CAsyncSocket直接封裝了低層的WinSock
API,簡化了WinSock編程,實現了一個異步操作的界面。如果希望某個操作是阻塞操作,則在調用Create時不要指定該操作對應的網絡事件。例如,希望Connect和Send是阻塞操作,在任務完成之后才返回,則可以使用如下的語句:</P>
<P align=justify>pSocket->Create(0, SOCK_STREAM, </P>
<P align=justify>FR_WRITE|FR_OOB|FR_ACCEPT|FR_CLOSE);</P>
<P
align=justify>這樣,在Connect和Send時,如果是用戶界面線程的話,可能阻塞線程消息循環。所以,最好在工作者線程中使用阻塞操作。</P>
<P align=justify></P>
<LI><A name=_Toc452641022></A><A name=_Toc457299162></A><B>CSocket</B>
<P></P>
<P
align=justify>如果希望在用戶界面線程中使用阻塞socket,則可以使用CSocket。它在非阻塞socket基礎之上實現了阻塞操作,在阻塞期間實現了消息循環。</P>
<P
align=justify>對于CSocket,處理網絡事件通知的函數OnAccept、OnClose、OnReceive仍然可以使用,OnConnect、OnSend在CSocket中永遠不會被調用,另外OnOutOfBandData在CSocket中不鼓勵使用。</P>
<P
align=justify>CSocket對象在調用Connect、Send、Accept、Close、Receive等成員函數后,這些函數在完成任務之后(連接被建立、數據被發送、連接請求被接收、socket被關閉、數據被讀取)之后才會返回。因此,Connect和Send不會導致OnConnect和OnSend被調用。如果覆蓋虛擬函數OnReceive、OnAccept、OnClose,不主動調用Receive、Accept、Close,則在網絡事件到達之后導致對應的虛擬函數被調用,虛擬函數的實現應該調用Receive、Accept、Close來完成操作。下面,就一個函數Receive來考察CSocket如何實現阻塞操作和消息循環的。</P>
<P align=justify>int CSocket::Receive(void* lpBuf, int nBufLen, int
nFlags)</P>
<P align=justify>{</P>
<P align=justify>//m_pbBlocking是CSocket的成員變量,用來標識當前是否正在進行</P>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -