日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

CAsyncSocket及CSocket的区别和用法

發布時間:2024/4/11 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 CAsyncSocket及CSocket的区别和用法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?微軟的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() 和Accept())直到操作完成后才返回控制權,因此如果需要低層控制和高效率,就使用CAsyncSock類;如果需要方便,則可使用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帶來的簡化編程的便利,也不會影響用戶界面對用戶的反應。?

? ? MFC疑難注解:CAsyncSocket及CSocket。CSocket從CAsyncSocket派生,但是其功能已經由異步轉換成同步。MFC對SOCKET編程的支持其實是很充分的,然而其文檔是語焉不詳的。以至于大多數用VC編寫的功能稍復雜的網絡程序,還是使用API的。故CAsyncSocket及CSocket事實上成為疑難,群眾多敬而遠之。余好事者也,不忍資源浪費,特為之注解。

一、CAsyncSocket與CSocket的區別

前者是異步通信,后者是同步通信;前者是非阻塞模式,后者是阻塞模式。另外,異步非阻塞模式有
時也被稱為長連接,同步阻塞模式則被稱為短連接。為了更明白地講清楚兩者的區別,舉個例子:

設想你是一位體育老師,需要測驗100位同學的400米成績。你當然不會讓100位同學一起起跑,因為當
同學們返回終點時,你根本來不及掐表記錄各位同學的成績。

如果你每次讓一位同學起跑并等待他回到終點你記下成績后再讓下一位起跑,直到所有同學都跑完。恭
喜你,你已經掌握了同步阻塞模式。

你設計了一個函數,傳入參數是學生號和起跑時間,返回值是到達終點的時間。你調用該函數100次,
就能完成這次測驗任務。這個函數是同步的,因為只要你調用它,就能得到結果;這個函數也是阻塞的,
因為你一旦調用它,就必須等待,直到它給你結果,不能去干其他事情。

如果你一邊每隔10秒讓一位同學起跑,直到所有同學出發完畢;另一邊每有一個同學回到終點就記錄成
績,直到所有同學都跑完。恭喜你,你已經掌握了異步非阻塞模式。

你設計了兩個函數,其中一個函數記錄起跑時間和學生號,該函數你會主動調用100次;另一個函數記
錄到達時間和學生號,該函數是一個事件驅動的callback函數,當有同學到達終點時,你會被動調用。
你主動調用的函數是異步的,因為你調用它,它并不會告訴你結果;這個函數也是非阻塞的,因為你一
旦調用它,它就馬上返回,你不用等待就可以再次調用它。但僅僅將這個函數調用100次,你并沒有完
成你的測驗任務,你還需要被動等待調用另一個函數100次。

當然,你馬上就會意識到,同步阻塞模式的效率明顯低于異步非阻塞模式。那么,誰還會使用同步阻塞
模式呢?

不錯,異步模式效率高,但更麻煩,你一邊要記錄起跑同學的數據,一邊要記錄到達同學的數據,而且
同學們回到終點的次序與起跑的次序并不相同,所以你還要不停地在你的成績冊上查找學生號。忙亂之
中你往往會張冠李戴。

你可能會想出更聰明的辦法:你帶了很多塊秒表,讓同學們分組互相測驗。恭喜你!你已經掌握了多線
程同步模式!

每個拿秒表的同學都可以獨立調用你的同步函數,這樣既不容易出錯,效率也大大提高,只要秒表足夠
多,同步的效率也能達到甚至超過異步。

可以理解,你現的問題可能是:既然多線程同步既快又好,異步模式還有存在的必要嗎?

很遺憾,異步模式依然非常重要,因為在很多情況下,你拿不出很多秒表。你需要通信的對端系統可能
只允許你建立一個SOCKET連接,很多金融、電信行業的大型業務系統都如此要求。

現在,你應該已經明白了:CAsyncSocket用于在少量連接時,處理大批量無步驟依賴性的業務。CSocket
用于處理步驟依賴性業務,或在可多連接時配合多線程使用。


二、CAsyncSocket異步機制

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

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

2、忙,Send()==SOCKET_ERROR,GetLastError()==WSAEWOULDBLOCK,導致這種情況的原因是,你的發送
緩沖區已被填滿或對方的接受緩沖區已被填滿。這種情況你實際上不用馬上理睬。因為CAsyncSocket會
記得你的Send WSAEWOULDBLOCK了,待發送的數據會寫入CAsyncSocket內部的發送緩沖區,并會在不忙的
時候自動調用OnSend,發送內部緩沖區里的數據。

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

4、完成,Send(pBuf,nLen)==nLen

與OnSend協助Send完成工作一樣,OnRecieve、OnConnect、OnAccept也會分別協助Recieve、Connect、
Accept完成工作。這一切都通過消息機制完成:

在你使用CAsyncSocket之前,必須調用AfxSocketInit初始化WinSock環境,而AfxSocketInit會創建一個
隱藏的CSocketWnd對象,由于這個對象由Cwnd派生,因此它能夠接收Windows消息。所以它能夠成為高層
CAsyncSocket對象與WinSock底層之間的橋梁。例如某CAsyncSocket在Send時WSAEWOULDBLOCK了,它就會
發送一條消息給CSocketWnd作為報告,CSocketWnd會維護一個報告登記表,當它收到底層WinSock發出的
空閑消息時,就會檢索報告登記表,然后直接調用報告者的OnSend函數。所以前文所說的CAsyncSocket會
自動調用OnXxx,實際上是不對的,真正的調用者是CSocketWnd——它是一個CWnd對象,運行在獨立的線
程中。

使用CAsyncSocket時,Send流程和Recieve流程是不同的,不理解這一點就不可能順利使用CAsyncSocket。

MSDN對CAsyncSocket的解釋很容易讓你理解為:只有OnSend被觸發時你Send才有意義,你才應該Send,
同樣只有OnRecieve被觸發時你才應該Recieve。很不幸,你錯了:

你會發現,連接建立的同時,OnSend就第一次被觸發了,嗯,這很好,但你現在還不想Send,你讓OnSend
返回,干點其他的事情,等待下一次OnSend試試看?實際上,你再也等不到OnSend被觸發了。因為,除
了第一次以外,OnSend的任何一次觸發,都源于你調用了Send,但碰到了WSAEWOULDBLOCK!

所以,使用CAsyncSocket時,針對發送的流程邏輯應該是:你需兩個成員變量,一個發送任務表,一個
記錄發送進度。你可以,也應該,在任何你需要的時候,主動調用Send來發送數據,同時更新任務表和
發送進度。而OnSend,則是你的負責擦屁股工作的助手,它被觸發時要干的事情就是根據任務表和發送
進度調用Send繼續發。若又沒能將任務表全部發送完成,更新發送進度,退出,等待下一次OnSend;若
任務表已全部發送完畢,則清空任務表及發送進度。

使用CAsyncSocket的接收流程邏輯是不同的:你永遠不需要主動調用Recieve,你只應該在OnRecieve中等
待。由于你不可能知道將要抵達的數據類型及次序,所以你需要定義一個已收數據表作為成員變量來存儲
已收到但尚未處理的數據。每次OnRecieve被觸發,你只需要被動調用一次Recieve來接受固定長度的數據,
并添加到你的已收數據表后。然后你需要掃描已收數據表,若其中已包含一條或數條完整的可解析的業務
數據包,截取出來,調用業務處理窗口的處理函數來處理或作為消息參數發送給業務處理窗口。而已收數
據表中剩下的數據,將等待下次OnRecieve中被再次組合、掃描并處理。

在長連接應用中,連接可能因為各種原因中斷,所以你需要自動重連。你需要根據CAsyncSocket的成員變
量m_hSocket來判斷當前連接狀態:if(m_hSocket==INVALID_SOCKET)。當然,很奇怪的是,即使連接已經
中斷,OnClose也已經被觸發,你還是需要在OnClose中主動調用Close,否則m_hSocket并不會被自動賦值
為INVALID_SOCKET。

在很多長連接應用中,除建立連接以外,還需要先Login,然后才能進行業務處理,連接并Login是一個步
驟依賴性過程,用異步方式處理反而會很麻煩,而CAsyncSocket是支持切換為同步模式的,你應該掌握在
適當的時候切換同異步模式的方法:

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派生,然后重載OnAccept:

//CListenSocket頭文件
class CListenSocket : public CSocket
{
public:
??? CListenSocket(HWND hWnd=NULL);
??? HWND m_hWnd; //事件處理窗口
??? virtual void OnAccept(int nErrorCode);
};

//CListenSocket實現文件
#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::OnSocketMsg(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臨時對象。俺沒有這么干過,估計是因為Attach方法
含有獲取CSocketWnd句柄的內置功能。

俺的解決方案還是使用自定義消息,比如俺不能在子線程中Close,那么,俺可以給主線程發送一條消息,
讓主線程的消息處理函數來完成Close,也很方便。

CSocket一般配合多線程使用,只要你想收發數據,你就可以創建一個CSocket對象,并創建一個子線程來
進行收發。所以被阻塞的只是子線程,而主線程總是可以隨時創建子線程去幫它干活。由于可能同時有很
多個CSocket對象在工作,所以你一般還要創建一個列表來儲存這些CSocket對象的標識,這樣你可能通過
在列表中檢索標識來區分各個CSocket對象,當然,由于內存地址的唯一性,對象指針本身就可以作為標識。


相對CAsyncSocket而言,CSocket的運作流程更直觀也更簡單。

四、技術內幕

? ? ? ?Socket有同步阻塞方式和異步非阻塞方式兩種使用,事實上同步和異步在我們編程的生涯中可能遇到了很多,而Socket也沒什么特別。雖然同步好用,不費勁,但不能滿足一些應用場合,其效率也很低。
? ? ? ?也許初涉編程的人不能理解“同步(或阻塞)”和“異步(或非阻塞)”,其實簡單兩句話就能講清楚,同步和異步往往都是針對一個函數來說的,“同步”就是函數直到其要執行的功能全部完成時才返回,而“異步”則是,函數僅僅做一些簡單的工作,然后馬上返回,而它所要實現的功能留給別的線程或者函數去完成。例如,SendMessage就是“同步”函數,它不但發送消息到消息隊列,還需要等待消息被執行完才返回;相反PostMessage就是個異步函數,它只管發送一個消息,而不管這個消息是否被處理,就馬上返回。
<一>、Socket?API
????首先應該知道,有Socket1.1提供的原始API函數,和Socket2.0提供的一組擴展函數,兩套函數。這兩套函數有重復,但是2.0提供的函數功能更強大,函數數量也更多。這兩套函數可以靈活混用,分別包含在頭文件Winsock.h,Winsock2.h,分別需要引入庫wsock32.lib、Ws2_32.lib。
1、默認用作同步阻塞方式,那就是當你從不調用WSAIoctl()和ioctlsocket()來改變Socket?IO模式,也從不調用WSAAsyncSelect()和WSAEventSelect()來選擇需要處理的Socket事件。正是由于函數accept(),WSAAccept(),connect(),WSAConnect(),send(),WSASend(),recv(),WSARecv()等函數被用作阻塞方式,所以可能你需要放在專門的線程里,這樣以不影響主程序的運行和主窗口的刷新。
2、如果作為異步用,那么程序主要就是要處理事件。它有兩種處理事件的辦法:
????第一種,它常關聯一個窗口,也就是異步Socket的事件將作為消息發往該窗口,這是由WinSock擴展規范里的一個函數WSAAsyncSelect()來實現和窗口關聯。最終你只需要處理窗口消息,來收發數據。
??第二種,用到了擴展規范里另一個關于事件的函數WSAEventSelect(),它是用事件對象的方式來處理Socket事件,也就是,你必須首先用WSACreateEvent()來創建一個事件對象,然后調用WSAEventSelect()來使得Socket的事件和這個事件對象關聯。最終你將要在一個線程里用WSAWaitForMultipleEvents()來等待這個事件對象被觸發。這個過程也稍顯復雜。
<二>、CAsyncSocket
????看類名就知道,它是一個異步非阻塞Socket封裝類,CAsyncSocket::Create()有一個參數指明了你想要處理哪些Socket事件,你關心的事件被指定以后,這個Socket默認就被用作了異步方式。CAsyncSocket是在UI線程中使用的,不需要多線程。那么CAsyncSocket內部到底是如何將事件交給你的呢?
????CAsyncSocket的Create()函數,除了創建了一個SOCKET以外,還創建了個CSocketWnd窗口對象,并使用WSAAsyncSelect()將這個SOCKET與該窗口對象關聯,以讓該窗口對象處理來自Socket的事件(消息),然而CSocketWnd收到Socket事件之后,只是簡單地回調CAsyncSocket::OnReceive(),CAsyncSocket::OnSend(),CAsyncSocket::OnAccept(),CAsyncSocket::OnConnect()等虛函數。所以CAsyncSocket的派生類,只需要在這些虛函數里添加發送和接收的代碼。
??
??簡化后,大致的代碼為:
??bool?CAsyncSocket::Create(?long?lEvent?)?file://參數lEvent是指定你所關心的Socket事件
??{
???m_hSocket?=?socket(?PF_INET,?SOCK_STREAM,?0?);?file://創/建Socket本身

???CSocketWnd*?pSockWnd?=?new?CSocketWnd;?file://創建響應事件的窗口,實際的這個窗口在AfxSockInit()調用時就被創建了。
???pSockWnd->Create(...);

???WSAAsyncSelect(?m_hSocket,?pSockWnd->m_hWnd,?WM_SOCKET_NOTIFY,?lEvent?);?file://Socket/事件和窗口關聯
??}
??
??static?void?PASCAL?CAsyncSocket::DoCallBack(WPARAM?wParam,?LPARAM?lParam)
??{
???CAsyncSocket?Socket;
???Socket.Attach(?(SOCKET)wParam?);?file://wParam/就是觸發這個事件的Socket的句柄
???int?nErrorCode?=?WSAGETSELECTERROR(lParam);?file://lParam/是錯誤碼與事件碼的合成
???switch?(WSAGETSELECTEVENT(lParam))
???{
???case?FD_READ:
????pSocket->OnReceive(nErrorCode);
????break;
???case?FD_WRITE:
????pSocket->OnSend(nErrorCode);
????break;
???case?FD_OOB:
????pSocket->OnOutOfBandData(nErrorCode);
????break;
???case?FD_ACCEPT:
????pSocket->OnAccept(nErrorCode);
????break;
???case?FD_CONNECT:
????pSocket->OnConnect(nErrorCode);
????break;
???case?FD_CLOSE:
????pSocket->OnClose(nErrorCode);
????break;
???}
??}

??CSocketWnd類大致為:

??BEGIN_MESSAGE_MAP(CSocketWnd,?CWnd)
???ON_MESSAGE(WM_SOCKET_NOTIFY,?OnSocketNotify)
??END_MESSAGE_MAP()

??LRESULT?CSocketWnd::OnSocketNotify(WPARAM?wParam,?LPARAM?lParam)
??{
???CAsyncSocket::DoCallBack(?wParam,?lParam?);?file://收/到Socket事件消息,回調CAsyncSocket的DoCallBack()函數
???return?0L;
??}

??然而,最不容易被初學Socket編程的人理解的,也是本文最要提醒的一點是,客戶方在使用CAsyncSocket::Connect()時,往往返回一個WSAEWOULDBLOCK的錯誤(其它的某些函數調用也如此),實際上這不應該算作一個錯誤,它是Socket提醒我們,由于你使用了非阻塞Socket方式,所以(連接)操作需要時間,不能瞬間建立。既然如此,我們可以等待呀,等它連接成功為止,于是許多程序員就在調用Connect()之后,Sleep(0),然后不停地用WSAGetLastError()或者CAsyncSocket::GetLastError()查看Socket返回的錯誤,直到返回成功為止。這是一種錯誤的做法,斷言,你不能達到預期目的。事實上,我們可以在Connect()調用之后等待CAsyncSocket::OnConnect()事件被觸發,CAsyncSocket::OnConnect()是要表明Socket要么連接成功了,要么連接徹底失敗了。至此,我們在CAsyncSocket::OnConnect()被調用之后就知道是否Socket連接成功了,還是失敗了。
??類似的,Send()如果返回WSAEWOULDBLOCK錯誤,我們在OnSend()處等待,Receive()如果返回WSAEWOULDBLOCK錯誤,我們在OnReceive()處等待,以此類推。
??還有一點,也許是個難點,那就是在客戶方調用Connect()連接服務方,那么服務方如何Accept(),以建立連接的問題。簡單的做法就是在監聽的Socket收到OnAccept()時,用一個新的CAsyncSocket對象去建立連接,例如:

?void?CMySocket::OnAccept(?int?ErrCode?)
?{
???????CMySocket*?pSocket?=?new?CMySocket;
???????Accept(?*pSocket?);
?}
????于是,上面的pSocket和客戶方建立了連接,以后的通信就是這個pSocket對象去和客戶方進行,而監聽的Socket仍然繼續在監聽,一旦又有一個客戶方要連接服務方,則上面的OnAccept()又會被調用一次。當然pSocket是和客戶方通信的服務方,它不會觸發OnAccept()事件,因為它不是監聽Socket。

<三>、CSocket
???CSocket是MFC在CAsyncSocket基礎上派生的一個同步阻塞Socket的封裝類。它是如何又把CAsyncSocket變成同步的,而且還能響應同樣的Socket事件呢?
??其實很簡單,CSocket在Connect()返回WSAEWOULDBLOCK錯誤時,不是在OnConnect(),OnReceive()這些事件終端函數里去等待。你先必須明白Socket事件是如何到達這些事件函數里的。這些事件處理函數是靠CSocketWnd窗口對象回調的,而窗口對象收到來自Socket的事件,又是靠線程消息隊列分發過來的。總之,Socket事件首先是作為一個消息發給CSocketWnd窗口對象,這個消息肯定需要經過線程消息隊列的分發,最終CSocketWnd窗口對象收到這些消息就調用相應的回調函數(OnConnect()等)。
???所以,CSocket在調用Connect()之后,如果返回一個WSAEWOULDBLOCK錯誤時,它馬上進入一個消息循環,就是從當前線程的消息隊列里取關心的消息,如果取到了WM_PAINT消息,則刷新窗口,如果取到的是Socket發來的消息,則根據Socket是否有操作錯誤碼,調用相應的回調函數(OnConnect()等)。
??大致的簡化代碼為:

??BOOL?CSocket::Connect(?...?)
??{
???if(?!CAsyncSocket::Connect(?...?)?)
???{
????if(?WSAGetLastError()?==?WSAEWOULDBLOCK?)?file://由/于異步操作需要時間,不能立即完成,所以Socket返回這個錯誤
????{
?????file://進/入消息循環,以從線程消息隊列里查看FD_CONNECT消息,直到收到FD_CONNECT消息,認為連接成功。
?????while(?PumpMessages(?FD_CONNECT?)?);
????}
???}
??}
??BOOL?CSocket::PumpMessages(?UINT?uEvent?)
??{
??????CWinThread*?pThread?=?AfxGetThread();
??????while(?bBlocking?)?file://bBlocking/僅僅是一個標志,看用戶是否取消對Connect()的調用
??????{
??????????MSG?msg;
??????????if(?PeekMessage(?&msg,?WM_SOCKET_NOTIFY?)?)
??????????{
?????????????if(?msg.message?==?WM_SOCKET_NOTIFY?&&?WSAGETSELECTEVENT(msg.lParam)?==?uStopFlag?)
?????????????{
?????????????????CAsyncSocket::DoCallBack(?msg.wParam,?msg.lParam?);
?????????????????return?TRUE;
?????????????}?????
?????????}
?????????else
????????{
?????????????OnMessagePending();?file://處/理消息隊列里的其它消息
?????????????pThread->OnIdle(-1);
????????}
?????}
??}
??BOOL?CSocket::OnMessagePending()
??{
??????MSG?msg;
???????if(?PeekMessage(?&msg,?NULL,?WM_PAINT,?WM_PAINT,?PM_REMOVE?)?)
???????{?file://這/里僅關心WM_PAINT消息,以處理阻塞期間的主窗口重畫
???????????::DispatchMessage(?&msg?);
???????????return?FALSE;
???????}
???????return?FALSE;
??}

???其它的CSocket函數,諸如Send(),Receive(),Accept()都在收到WSAEWOULDBLOCK錯誤時,進入PumpMessages()消息循環,這樣一個原本異步的CAsyncSocket,到了派生類CSocket,就變成同步的了。
??明白之后,我們可以對CSocket應用自如了。比如有些程序員將CSocket的操作放入一個線程,以實現多線程的異步Socket(通常,同步+多線程?相似于?異步?)。

<四>、CSocketFile
??另外,進行Socket編程,不能不提到CSocketFile類,其實它并不是用來在Socket雙方發送文件的,而是將需要序列化的數據,比如一些結構體數據,傳給對方,這樣,程序的CDocument()的序列化函數就完全可以和CSocketFile聯系起來。例如你有一個CMyDocument實現了Serialize(),你可以這樣來將你的文檔數據傳給Socket的另一方:

?CSocketFile?file(?pSocket?);
?CArchive?ar(?&file,?CArchive::store?);
?pDocument->Serialize(?ar?);
?ar.Close();

??同樣,接收一方可以只改變上面的代碼為CArchive?ar(?&file,?CArchive::load?);即可。
???注意到,CSocketFile類雖然從CFile派生,但它屏蔽掉了CFile::Open()等函數,而函數里僅扔出一個例外。那么也就是說,你不能調用CSocketFile的Open函數來打開一個實實在在的文件,否則會導致例外,如果你需要利用CSocketFile來傳送文件,你必須提供CSocketFile類的這些函數的實現。
??再一點,CArchive不支持在datagram的Socket連接上序列化數據.

總結

以上是生活随笔為你收集整理的CAsyncSocket及CSocket的区别和用法的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。