MFC對Socket編程的支援

來源:互聯網
上載者:User

標籤:style   blog   http   color   os   使用   io   檔案   ar   

MFC對Socket編程的支援其實是很充分的,然而其文檔是語焉不詳的.以至於大多數用Visual C++編寫的功能稍複雜的網路程式,還是使用其API的.故CAsyncSocket及CSocket事實上成為了疑難,群眾多敬而遠之.餘好事者也,不忍資源浪費,特為之註解.

 

1.CAsyncSocket與CSocket的區別是前者是非同步通訊,後者是同步通訊.前者是非阻塞模式,後者是阻塞模式.另外,非同步非阻塞模式有時也被成為長串連,同步阻塞模式則被成為短串連.

為了更明白的講清楚兩者的區別.舉個例子.設想你是一位體育老師,需要測驗100位同學的400米成績.你當然不會讓100位同學一起起跑.因為當同學們返回終點時,你根本來不及掐表記錄各位同學的成績.

如果你每次讓一位同學起跑並等待他回到終點,你記下成績後再讓下一位起跑,直到所有同學都跑完.恭喜你,你已經掌握了同步阻塞模式.你設計了一個函數,傳入參數是學生號和起跑時間,傳回值是到達終點的時間.你調用該函數100次,就能完成此次測驗任務.這個函數是同步的.因為你調用它,就能得到結果.這個函數也是阻塞的.因為你一旦調用它,就必須等待,直到它給你結果,不能去幹其他事情.

如果你一邊每隔10秒讓一位同學起跑,直到所有同學出發完畢.另一邊,每有一個同學回到終點,就記錄成績,直到所有同學都跑完.恭喜你,你已經掌握了非同步非阻塞模式.你設計了兩個函數,其中一個函數記錄了起跑時間和學生號,該函數是一個事件驅動的CallBack函數.當有同學到達終點時,你會被動調用/你主動調用的函數是非同步.因為你調用它,它並不會告訴你結果.這個函數也是非阻塞的.因為你一旦調用它,它就馬上返回.不用等待就可以再次調用它.但僅僅將這個函數調用100次,你並沒有完成你的測驗任務.你還需被動等待調用另一個函數100次.當然,你馬上就會意識到,同步阻塞模式的效率明顯低於非同步非阻塞模式.那麼,還有誰會使用同步阻塞模式呢?不錯,非同步模式效率高,但更麻煩,你一邊要記錄起跑同學的資料,一邊還要記錄到達同學的資料.而且同學們回到終點的次序和起跑的次序並不相同.所以,你還要不停的在你的成績冊上尋找學生號.忙亂中你往往會張冠李戴.你可能會想出更聰明的辦法.你帶了很多塊秒錶,讓同學們分組相互測驗.恭喜你.你已經掌握了多線程同步模式.每個拿秒錶的同學都可以獨立調用你的同步模式.這樣既不會出錯,效率也大大提高.只要秒錶足夠多,同步的效率也能達到,甚至超過非同步.可以理解.你現在的問題是:既然多線程既快又好,非同步模式還有存在的必要嗎?很遺憾,非同步模式依然非常重要,因為在很多情況下,你拿不出秒錶.你需要通訊的對端系統可能只允許你建立一個Socket串連.很多金融,電信行業的大型系統都如此要求.現在,你應該已經明白了:CAsyncSocket用於在少量串連時,處理大批量無步驟依賴性的業務.CSocket用於處理步驟依賴性業務,或在多串連時配合多線程使用.

 

2. CAsyncSocket非同步機制當你獲得了一個非同步串連後,實際上你掃除了發送動作與接收動作之間的依賴性.所有你隨時可以發包,也隨時可能收到包.發送,接收函數都是非同步非阻塞的,頃刻就能返回,所以收發交錯進行著.你可以一直工作,保持很高的效率.但是,正因為發送,接收函數都是非同步非阻塞的.所以僅調用它們並不能保障發送或接收的完成.例如,發送函數Send,調用它可能有4種情況.

(1)錯誤,Send()==SOCKET_ERROR,GetLastError()!=WSAEWOULDBLOCK,這種情況可能由各種網路問題導致,你需要馬上決定是放棄本次操作,還是啟用某種對策.

(2)忙,Send()==SOCKET_ERROR,GetLastError()!=WSAEWOUDLDBLOCK,導致這種情況的原因是,你的發送緩衝區已被填滿或對方的接收緩衝區已被填滿.這種情況你實際不用馬上理睬.因為CAsyncSocket會記得你的Send WSAEWOULDBLOCK了,待發送的資料會寫入CAsyncSocket內部的發送緩衝區,並會在不忙的時候自動調用OnSend,發送內部緩衝區裡的資料.

(3)部分完成,0<Send(pBuf,nLen)<nLen,導致這種情況的原因是,你的發送緩衝區或對方的接收緩衝區中剩餘的空位不足以容納你這次需要發送的全部資料.處理這種情況的通常做法是繼續發送尚未發送的資料,直到全部完成或WSAEWOULDBLOCK.這種情況很容易讓人產生疑惑,既然緩衝區空位不足,那麼本次發送就已經填滿了緩衝區,幹嘛還要繼續發送呢?就像WSAEWOULDBLOCK了一樣直接交給OnSend去處理剩餘資料的發送不是更合理嗎?然而很遺憾,CAsyncSocket不會記得你只完成了部分發送任務,從而在合適的時候觸發OnSend,因為你並沒有WSAEWOULDBLOCK,其實不然,假如WSAEWOULDBLOCK是由於對方讀取接收緩衝區不及時引起的,繼續發送的確很可能會WSAEWOULDBLOCK,但假如WSAEWOULDBLOCK是由於發送緩衝區被填滿了,就不一定了,因為你的網卡處理髮送緩衝區中資料的速度不見得比你往發送緩衝區拷貝資料的速度更慢,這要取決於你競爭CPU,記憶體,頻寬資源的其他應用程式的具體情況.假如這時候CPU負載較大而網卡負載較低,則雖然剛剛發送緩衝區是滿的,你繼續發送也不會WSAEWOULDBLOCK.

(4)完成.Send(pBuf,nLen)==nLen與OnSend協助Send完成工作一樣.OnReceive,OnConnect,OnAccept也會分別協助Receive,Connect,Accept完成工作.這一切都通過訊息機制完成.在你使用 CAsyncSocket之前,必須調用AfxSocketInit初始化WinSock環境,而AfxSocketInit會建立一個隱藏的CSocketWnd對象,由於這個對象由Cwnd派生,因此它能夠接收Windows訊息.所以它能夠成為高層CAsyncSocket對象與WinSock底層之間的橋樑.例如某CAsyncSocket在Send時WSAEWOULDBLOCK了,它就會發送一條訊息給CSocketWnd作為報告,CSocketWnd會維護一個報告登記表,當它收到底層WinSock發送的空閑訊息時,就會檢索報告登記表,然後直接調用報告者的OnSend函數.所以前文所說的 CAsyncSocket會自動調用OnXXX,實際上是不對的.使用 CAsyncSocket時,Send流程和Receive流程是不同的.不理解這點就不可能順利使用 CAsyncSocket.MSDN對 CAsyncSocket的理解很容易讓你理解為:只有OnSend被觸發時,你Send才有意義,你才應該Send,同樣只有OnReceive被觸發時你才應該Receive.很不幸,你錯了.你會發現,串連建立的同時,OnSend就第一次被觸發了.恩,這很好,但你現在還不想Send,你讓OnSend返回,幹其他的事情.等待下一次OnSend試試看?實際上,你再也等不到OnSend被觸發了.因為,除了第一次以外,OnSend的任何一次觸發,都源於你調用了Send,但碰到了WSAEWOULDBLOCK!所以,使用CAsyncSocket時,針對發送的流程邏輯是:你需要兩個成員變數,一個發送任務表,一個記錄發送進度.你可以,也應該,在任何你需要的時候,主動調用Send來發送資料,同時更新任務表和發送進度.而OnSend,則是你的負責擦屁股工作的助手,它被觸發時要乾的事情就是根據任務表和發送進度調用OnSend.若任務表已經發送完畢,則清空任務表及發送進度.使用 CAsyncSocket的接收流程邏輯是不同的.你永遠不需要主動調用Recieve,你只應該在OnRecieve中等待.由於你不可能知道將要抵達的資料類型及次序,所以你需要定義一個已收資料表作為成員變數來儲存已收到但尚未處理的資料.每次OnRecieve被觸發,你只需要被動調用一次Recieve來接受固定長度的資料,並添加到你的已收資料表後,然後你需要掃描已收資料表,若其中已包含一條或數條完整的可解析的業務資料包,截取出來,調用業務處理視窗的處理函數來處理或作為訊息參數發送給業務處理視窗.而已收資料表中剩下的資料,將等待下次OnRecieve中被再次組合,掃描並處理.在長串連應用中,串連可能因為各種原因中斷,所以你需要自動重連.你需要根據CAsyncSocket的成員變數m_hSocket來判斷當前串連狀態.

?
1 if(m_hSocket==INVALID_SOCKET)

 

當然,很奇怪的是,即使串連已經中斷,OnClose也已經被觸發,你還是需要在OnClose中主動調用Close,否則m_hSocket並不會被自動賦值為INVALID_SOCKET.在很多長串連應用中,除建立串連以外,還需要先Login,然後才能進行業務處理,串連並Login是一個步驟依賴性過程,用非同步處理反而會很麻煩,而CAsyncSocket是支援切換為同步模式的,你應該掌握在適當的時候切換同非同步模式的方法.

?
123456 DWORD dw;//切換為同步模式 dw=0; IOCtl(FIONBIO,&dw); ...//切換回非同步模式 dw=1; IOCtl(FIONBIO,&dw);

三.CSocket的用法.  

 

CSocket在CAsyncSocket的基礎上,修改了Send,Recieve等成員函數,幫你內建了一個用以輪詢收發緩衝區的迴圈,變成了同步短串連模式.短串連應用簡單明了,CSocket經常不用派生就可以直接使用,但也有些問題.

1.用作監聽的時候曾經看到有人自己建立線程,線上程中建立CSocket對象進行Listen,Accept.若Accept成功,則再起一個線程繼續Listen,Accept...可以說他完全不瞭解CSocket,實際上CSocket的監聽機制已經內建了多線程機制,你只需要從CSocket派生,然後重載.

?
123456789 OnAccept: //CListenSocket標頭檔 Class CListenSocket:public CSocket { public: CListenSocket(HWND hWnd=NULL); HWND m_hWnd;//事件處理視窗 Virtual void OnAccept(int nErrorCode); } //CListenSocket實現檔案
?
1234567891011121314151617181920212223242526272829303132 #include "ListenSocket.h"   CListenSocket::CListenSocket(HWND hWnd) { m_hWnd=hWnd; }   //主線程 void CListenSocket::OnAccept(int nErrorCode) { SendMessage(m_hWnd,WM_SOCKET_MSG,SOCKET_CLNT_ACCEPT,0); CSocket::OnAccept(nErrorCode); } ... m_pListenSocket=new CListenSocket(m_hWnd); m_pListenSocket->Create(...); m_pListenSocket->Listen(); ... LRESULT CXXXDlg:OnSocket(WPARAM wParam, LPARAM lParam); { UINT type=(UINT)wParam; switch (type) { case SOCKET_CLNT_ACCEPT: CSocket* pSocket=new CSocket; if (!m_pListenSocket->Accept(*pSocket)) { delete pSocket; break; } } }

2.用於多線程的時候常看到人說CSocket在子線程中不能用,其實不然.實際情況是:直接使用CSocket動態建立的對象,將其指標作為參數傳遞給子線程,則子線程中進行收發等各種操作都沒問題.但如果是使用CSocket衍生類別建立的對象,就要看你重載了哪些方法,假如你僅重載了OnClose,則子線程中你也可以收發,但是不能Close!因為CSocket是用內部迴圈做到同步的,並不依賴個OnXXX,它不需要與CSocketWnd互動.但當你派生並重載OnXXX後,它為了提供訊息機制就必須與CSocketWnd互動.當你調用AfxSocketInit時,你的主線程會獲得一個訪問CSocketWnd的控制代碼,對CSocketWnd的訪問是MFC幫你自動完成,是被隱藏的.而你自己建立的子線程並不自動具備訪問CSocketWnd的機制,所以子線程需要訪問CSocketWnd的控制代碼都會失敗.常看到的解決辦法是給子線程傳遞SOCKET控制代碼而不是CSocket對象指標,然後在子線程中建立CSocket臨時對象並Attach傳入的控制代碼,用完後再Dettach並delete臨時對象.俺沒有這麼幹過,估計是因為Attch防範含有擷取CSocketWnd控制代碼的內建功能.俺的解決方案還是使用自訂訊息,比如俺不能在子線程中Close.那麼,俺可以給主線程發送一條訊息.讓主線程的訊息處理函數來完成Close.也很方便.CSocket一般配合多線程使用,並要你想收發訊息,你就可以建立一個CSocket對象,並建立一個子線程來進行收發.所以被阻塞的只是子線程,而主線程總是可以隨時建立子線程去幫他幹活.由於可能同時有很多個CSocket對象在工作,所以你一般還要建立一個列表來儲存這些CSocket對象的標識沒這樣你可能通過在列表中檢索標識來區分各個CSocket對象,當然,由於記憶體位址的唯一性,對象指標本身就可以作為標識.相對CAsyncSocket而言,CSocket的運作流程更直觀也更簡單,至於CSocketFile,CArchive之類的,似乎也不需要多說什麼,就這樣結束吧.

MFC對Socket編程的支援

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.