CAsyncSocket及CSocket的區別和用法分析_第1頁
CAsyncSocket及CSocket的區別和用法分析_第2頁
CAsyncSocket及CSocket的區別和用法分析_第3頁
CAsyncSocket及CSocket的區別和用法分析_第4頁
CAsyncSocket及CSocket的區別和用法分析_第5頁
已閱讀5頁,還剩8頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、微軟的MFC把復雜的WinSock API函數封裝到類里,這使得編寫網絡應用程序更容易。CAsyncSocket類逐個封裝了WinSock API,為高級網絡程序員提供了更加有力而靈活的方法。這個類基于程序員了解網絡通訊的假設,目的是為了在MFC中使用WinSock,程序員有責任處理諸如阻塞、字節順序和在Unicode與MBCS 間轉換字符的任務。為了給程序員提供更方便的接口以自動處理這些任務,MFC給出了CSocket類,這個類是由CAsyncSocket類繼承下來的,它提供了比CAsyncSocket更高層的WinSock API接口。CSocket類和CSocketFile類可以與CAr

2、chive類一起合作來管理發送和接收的數據,這使管理數據收發更加便利。CSocket對象提供阻塞模式,這對于CArchive的同步操作是至關重要的。阻塞函數(如Receive()、Send()、ReceiveFrom()、SendTo() 和Accept())直到操作完成后才返回控制權,因此如果需要低層控制和高效率,就使用CAsyncSock類;如果需要方便,則可使用CSocket類。CSocket類是由CAsyncSocket繼承而來的,事實上,在MFC中CAsyncSocket 逐個封裝了WinSock API,每個CAsyncSocket對象代表一個Windows Socket對象,使用

3、CAsyncSocket 類要求程序員對網絡編程較為熟悉。相比起來,CSocket類是CAsyncSocket的派生類,繼承了它封裝的WinSock API。一個CSocket對象代表了一個比CAsyncSocket對象更高層次的Windows Socket的抽象,CSocket類與CSocketFile類和CArchive類一起工作來發送和接收數據,因此使用它更加容易使用。CSocket對象提供阻塞模式,因為阻塞功能對于CArchive的同步操作是至關重要的。在這里有必要對阻塞的概念作一解釋:一個socket可以處于阻塞模式或非阻塞模式,當一個套接字處于阻塞模式(即同步操作)時,它的阻塞函數

4、直到操作完成才會返回控制權,之所以稱為阻塞是因為此套接字的阻塞函數在完成操作返回之前什么也不能做。如果一個socket處于非阻塞模式(即異步操作),則會被調用函數立即返回。在CAsyncSocket類中可以用GetLastError 成員函數查詢最后的錯誤,如果錯誤是WSAEWOULDBLOCK則說明有阻塞,而CSocket絕不會返回WSAEWOULDBLOCK,因為它自己管理阻塞。微軟建議盡量使用非阻塞模式,通過網絡事件的發生而通知應用程序進行相應的處理。但在CSocket類中,為了利用CArchive 處理通訊中的許多問題和簡化編程,它的一些成員函數總是具有阻塞性質的,這是因為CArchi

5、ve類需要同步的操作。在Win32環境下,如果要使用具有阻塞性質的套接字,應該放在獨立的工作線程中處理,利用多線程的方法使阻塞不至于干擾其他線程,也不會把CPU時間浪費在阻塞上。多線程的方法既可以使程序員享受CSocket帶來的簡化編程的便利,也不會影響用戶界面對用戶的反應。 MFC疑難注解:CAsyncSocket及CSocket。CSocket從CAsyncSocket派生,但是其功能已經由異步轉換成同步。MFC對SOCKET編程的支持其實是很充分的,然而其文檔是語焉不詳的。以至于大多數用VC編寫的功能稍復雜的網絡程序,還是使用API的。故CAsyncSocket及CSocket事實上成為

6、疑難,群眾多敬而遠之。余好事者也,不忍資源浪費,特為之注解。一、CAsyncSocket與CSocket的區別前者是異步通信,后者是同步通信;前者是非阻塞模式,后者是阻塞模式。另外,異步非阻塞模式有時也被稱為長連接,同步阻塞模式則被稱為短連接。為了更明白地講清楚兩者的區別,舉個例子:設想你是一位體育老師,需要測驗100位同學的400米成績。你當然不會讓100位同學一起起跑,因為當同學們返回終點時,你根本來不及掐表記錄各位同學的成績。如果你每次讓一位同學起跑并等待他回到終點你記下成績后再讓下一位起跑,直到所有同學都跑完。恭喜你,你已經掌握了同步阻塞模式。你設計了一個函數,傳入參數是學生號和起跑時

7、間,返回值是到達終點的時間。你調用該函數100次,就能完成這次測驗任務。這個函數是同步的,因為只要你調用它,就能得到結果;這個函數也是阻塞的,因為你一旦調用它,就必須等待,直到它給你結果,不能去干其他事情。如果你一邊每隔10秒讓一位同學起跑,直到所有同學出發完畢;另一邊每有一個同學回到終點就記錄成績,直到所有同學都跑完。恭喜你,你已經掌握了異步非阻塞模式。你設計了兩個函數,其中一個函數記錄起跑時間和學生號,該函數你會主動調用100次;另一個函數記錄到達時間和學生號,該函數是一個事件驅動的callback函數,當有同學到達終點時,你會被動調用。你主動調用的函數是異步的,因為你調用它,它并不會告訴

8、你結果;這個函數也是非阻塞的,因為你一旦調用它,它就馬上返回,你不用等待就可以再次調用它。但僅僅將這個函數調用100次,你并沒有完成你的測驗任務,你還需要被動等待調用另一個函數100次。當然,你馬上就會意識到,同步阻塞模式的效率明顯低于異步非阻塞模式。那么,誰還會使用同步阻塞模式呢?不錯,異步模式效率高,但更麻煩,你一邊要記錄起跑同學的數據,一邊要記錄到達同學的數據,而且同學們回到終點的次序與起跑的次序并不相同,所以你還要不停地在你的成績冊上查找學生號。忙亂之中你往往會張冠李戴。你可能會想出更聰明的辦法:你帶了很多塊秒表,讓同學們分組互相測驗。恭喜你!你已經掌握了多線程同步模式!每個拿秒表的同

9、學都可以獨立調用你的同步函數,這樣既不容易出錯,效率也大大提高,只要秒表足夠多,同步的效率也能達到甚至超過異步。可以理解,你現的問題可能是:既然多線程同步既快又好,異步模式還有存在的必要嗎?很遺憾,異步模式依然非常重要,因為在很多情況下,你拿不出很多秒表。你需要通信的對端系統可能只允許你建立一個SOCKET連接,很多金融、電信行業的大型業務系統都如此要求。現在,你應該已經明白了:CAsyncSocket用于在少量連接時,處理大批量無步驟依賴性的業務。CSocket用于處理步驟依賴性業務,或在可多連接時配合多線程使用。二、CAsyncSocket異步機制當你獲得了一個異步連接后,實際上你掃除了發

10、送動作與接收動作之間的依賴性。所以你隨時可以發包,也隨時可能收到包。發送、接收函數都是異步非阻塞的,頃刻就能返回,所以收發交錯進行著,你可以一直工作,保持很高的效率。但是,正因為發送、接收函數都是異步非阻塞的,所以僅調用它們并不能保障發送或接收的完成。例如發送函數Send,調用它可能有4種結果:1、錯誤,Send()=SOCKET_ERROR,GetLastError()!=WSAEWOULDBLOCK,這種情況可能由各種網絡問題導致,你需要馬上決定是放棄本次操作,還是啟用某種對策2、忙,Send()=SOCKET_ERROR,GetLastError()=WSAEWOULDBLOCK,導致這

11、種情況的原因是,你的發送緩沖區已被填滿或對方的接受緩沖區已被填滿。這種情況你實際上不用馬上理睬。因為CAsyncSocket會記得你的Send WSAEWOULDBLOCK了,待發送的數據會寫入CAsyncSocket內部的發送緩沖區,并會在不忙的時候自動調用OnSend,發送內部緩沖區里的數據。3、部分完成,0Send(pBuf,nLen)Create(.);m_pListenSocket-Listen();.LRESULT CXxxDlg:OnSocketMsg(WPARAM wParam, LPARAM lParam) UINT type=(UINT)wParam; switch(typ

12、e) case SOCKET_CLNT_ACCEPT: CSocket* pSocket=new CSocket; if(!m_pListenSocket-Accept(*pSocket) delete pSocket; break; . . 2、用于多線程的時候常看到人說CSocket在子線程中不能用,其實不然。實際情況是:直接使用CSocket動態創建的對象,將其指針作為參數傳遞給子線程,則子線程中進行收發等各種操作都沒問題。但如果是使用CSocket派生類創建的對象,就要看你重載了哪些方法,假如你僅重載了OnClose,則子線程中你也可以正常收發,但不能Close!因為CSocket是用

13、內部循環做到同步的,并不依賴各OnXxx,它不需要與CSocketWnd交互。但當你派生并重載OnXxx后,它為了提供消息機制就必須與CSocketWnd交互。當你調用AfxSocketInit時,你的主線程會獲得一個訪問CSocketWnd的句柄,對CSocketWnd的訪問是MFC自動幫你完成的,是被隱藏的。而你自己創建的子線程并不自動具備訪問CSocketWnd的機制,所以子線程中需要訪問CSocketWnd的操作都會失敗。常看到的解決辦法是給子線程傳遞SOCKET句柄而不是CSocket對象指針,然后在子線程中創建CSocket臨時對象并Attach傳入的句柄,用完后再Dettach并

14、delete臨時對象。俺沒有這么干過,估計是因為Attach方法含有獲取CSocketWnd句柄的內置功能。俺的解決方案還是使用自定義消息,比如俺不能在子線程中Close,那么,俺可以給主線程發送一條消息,讓主線程的消息處理函數來完成Close,也很方便。CSocket一般配合多線程使用,只要你想收發數據,你就可以創建一個CSocket對象,并創建一個子線程來進行收發。所以被阻塞的只是子線程,而主線程總是可以隨時創建子線程去幫它干活。由于可能同時有很多個CSocket對象在工作,所以你一般還要創建一個列表來儲存這些CSocket對象的標識,這樣你可能通過在列表中檢索標識來區分各個CSocket

15、對象,當然,由于內存地址的唯一性,對象指針本身就可以作為標識。相對CAsyncSocket而言,CSocket的運作流程更直觀也更簡單。四、技術內幕 Socket有同步阻塞方式和異步非阻塞方式兩種使用,事實上同步和異步在我們編程的生涯中可能遇到了很多,而Socket也沒什么特別。雖然同步好用,不費勁,但不能滿足一些應用場合,其效率也很低。 也許初涉編程的人不能理解“同步(或阻塞)”和“異步(或非阻塞)”,其實簡單兩句話就能講清楚,同步和異步往往都是針對一個函數來說的,“同步”就是函數直到其要執行的功能全部完成時才返回,而“異步”則是,函數僅僅做一些簡單的工作,然后馬上返回,而它所要實現的功能留

16、給別的線程或者函數去完成。例如,SendMessage就是“同步”函數,它不但發送消息到消息隊列,還需要等待消息被執行完才返回;相反PostMessage就是個異步函數,它只管發送一個消息,而不管這個消息是否被處理,就馬上返回。、SocketAPI首先應該知道,有Socket1.1提供的原始API函數,和Socket2.0提供的一組擴展函數,兩套函數。這兩套函數有重復,但是2.0提供的函數功能更強大,函數數量也更多。這兩套函數可以靈活混用,分別包含在頭文件Winsock.h,Winsock2.h,分別需要引入庫wsock32.lib、Ws2_32.lib。1、默認用作同步阻塞方式,那就是當你從

17、不調用WSAIoctl()和ioctlsocket()來改變SocketIO模式,也從不調用WSAAsyncSelect()和WSAEventSelect()來選擇需要處理的Socket事件。正是由于函數accept(),WSAAccept(),connect(),WSAConnect(),send(),WSASend(),recv(),WSARecv()等函數被用作阻塞方式,所以可能你需要放在專門的線程里,這樣以不影響主程序的運行和主窗口的刷新。2、如果作為異步用,那么程序主要就是要處理事件。它有兩種處理事件的辦法:第一種,它常關聯一個窗口,也就是異步Socket的事件將作為消息發往該窗口,

18、這是由WinSock擴展規范里的一個函數WSAAsyncSelect()來實現和窗口關聯。最終你只需要處理窗口消息,來收發數據。第二種,用到了擴展規范里另一個關于事件的函數WSAEventSelect(),它是用事件對象的方式來處理Socket事件,也就是,你必須首先用WSACreateEvent()來創建一個事件對象,然后調用WSAEventSelect()來使得Socket的事件和這個事件對象關聯。最終你將要在一個線程里用WSAWaitForMultipleEvents()來等待這個事件對象被觸發。這個過程也稍顯復雜。、CAsyncSocket看類名就知道,它是一個異步非阻塞Socket封

19、裝類,CAsyncSocket:Create()有一個參數指明了你想要處理哪些Socket事件,你關心的事件被指定以后,這個Socket默認就被用作了異步方式。CAsyncSocket是在UI線程中使用的,不需要多線程。那么CAsyncSocket內部到底是如何將事件交給你的呢?CAsyncSocket的Create()函數,除了創建了一個SOCKET以外,還創建了個CSocketWnd窗口對象,并使用WSAAsyncSelect()將這個SOCKET與該窗口對象關聯,以讓該窗口對象處理來自Socket的事件(消息),然而CSocketWnd收到Socket事件之后,只是簡單地回調CAsync

20、Socket:OnReceive(),CAsyncSocket:OnSend(),CAsyncSocket:OnAccept(),CAsyncSocket:OnConnect()等虛函數。所以CAsyncSocket的派生類,只需要在這些虛函數里添加發送和接收的代碼。簡化后,大致的代碼為:boolCAsyncSocket:Create(longlEvent)file:/參數lEvent是指定你所關心的Socket事件m_hSocket=socket(PF_INET,SOCK_STREAM,0);file:/創/建Socket本身CSocketWnd*pSockWnd=newCSocketWnd

21、;file:/創建響應事件的窗口,實際的這個窗口在AfxSockInit()調用時就被創建了。pSockWnd-Create(.);WSAAsyncSelect(m_hSocket,pSockWnd-m_hWnd,WM_SOCKET_NOTIFY,lEvent);file:/Socket/事件和窗口關聯staticvoidPASCALCAsyncSocket:DoCallBack(WPARAMwParam,LPARAMlParam)CAsyncSocketSocket;Socket.Attach(SOCKET)wParam);file:/wParam/就是觸發這個事件的Socket的句柄int

22、nErrorCode=WSAGETSELECTERROR(lParam);file:/lParam/是錯誤碼與事件碼的合成switch(WSAGETSELECTEVENT(lParam)caseFD_READ:pSocket-OnReceive(nErrorCode);break;caseFD_WRITE:pSocket-OnSend(nErrorCode);break;caseFD_OOB:pSocket-OnOutOfBandData(nErrorCode);break;caseFD_ACCEPT:pSocket-OnAccept(nErrorCode);break;caseFD_CONN

23、ECT:pSocket-OnConnect(nErrorCode);break;caseFD_CLOSE:pSocket-OnClose(nErrorCode);break;CSocketWnd類大致為:BEGIN_MESSAGE_MAP(CSocketWnd,CWnd)ON_MESSAGE(WM_SOCKET_NOTIFY,OnSocketNotify)END_MESSAGE_MAP()LRESULTCSocketWnd:OnSocketNotify(WPARAMwParam,LPARAMlParam)CAsyncSocket:DoCallBack(wParam,lParam);file:/

24、收/到Socket事件消息,回調CAsyncSocket的DoCallBack()函數return0L;然而,最不容易被初學Socket編程的人理解的,也是本文最要提醒的一點是,客戶方在使用CAsyncSocket:Connect()時,往往返回一個WSAEWOULDBLOCK的錯誤(其它的某些函數調用也如此),實際上這不應該算作一個錯誤,它是Socket提醒我們,由于你使用了非阻塞Socket方式,所以(連接)操作需要時間,不能瞬間建立。既然如此,我們可以等待呀,等它連接成功為止,于是許多程序員就在調用Connect()之后,Sleep(0),然后不停地用WSAGetLastError()或

25、者CAsyncSocket:GetLastError()查看Socket返回的錯誤,直到返回成功為止。這是一種錯誤的做法,斷言,你不能達到預期目的。事實上,我們可以在Connect()調用之后等待CAsyncSocket:OnConnect()事件被觸發,CAsyncSocket:OnConnect()是要表明Socket要么連接成功了,要么連接徹底失敗了。至此,我們在CAsyncSocket:OnConnect()被調用之后就知道是否Socket連接成功了,還是失敗了。類似的,Send()如果返回WSAEWOULDBLOCK錯誤,我們在OnSend()處等待,Receive()如果返回WSA

26、EWOULDBLOCK錯誤,我們在OnReceive()處等待,以此類推。還有一點,也許是個難點,那就是在客戶方調用Connect()連接服務方,那么服務方如何Accept(),以建立連接的問題。簡單的做法就是在監聽的Socket收到OnAccept()時,用一個新的CAsyncSocket對象去建立連接,例如:voidCMySocket:OnAccept(intErrCode)CMySocket*pSocket=newCMySocket;Accept(*pSocket);于是,上面的pSocket和客戶方建立了連接,以后的通信就是這個pSocket對象去和客戶方進行,而監聽的Socket仍然

27、繼續在監聽,一旦又有一個客戶方要連接服務方,則上面的OnAccept()又會被調用一次。當然pSocket是和客戶方通信的服務方,它不會觸發OnAccept()事件,因為它不是監聽Socket。、CSocketCSocket是MFC在CAsyncSocket基礎上派生的一個同步阻塞Socket的封裝類。它是如何又把CAsyncSocket變成同步的,而且還能響應同樣的Socket事件呢?其實很簡單,CSocket在Connect()返回WSAEWOULDBLOCK錯誤時,不是在OnConnect(),OnReceive()這些事件終端函數里去等待。你先必須明白Socket事件是如何到達這些事件

28、函數里的。這些事件處理函數是靠CSocketWnd窗口對象回調的,而窗口對象收到來自Socket的事件,又是靠線程消息隊列分發過來的。總之,Socket事件首先是作為一個消息發給CSocketWnd窗口對象,這個消息肯定需要經過線程消息隊列的分發,最終CSocketWnd窗口對象收到這些消息就調用相應的回調函數(OnConnect()等)。所以,CSocket在調用Connect()之后,如果返回一個WSAEWOULDBLOCK錯誤時,它馬上進入一個消息循環,就是從當前線程的消息隊列里取關心的消息,如果取到了WM_PAINT消息,則刷新窗口,如果取到的是Socket發來的消息,則根據Socke

29、t是否有操作錯誤碼,調用相應的回調函數(OnConnect()等)。大致的簡化代碼為:BOOLCSocket:Connect(.)if(!CAsyncSocket:Connect(.)if(WSAGetLastError()=WSAEWOULDBLOCK)file:/由/于異步操作需要時間,不能立即完成,所以Socket返回這個錯誤file:/進/入消息循環,以從線程消息隊列里查看FD_CONNECT消息,直到收到FD_CONNECT消息,認為連接成功。while(PumpMessages(FD_CONNECT);BOOLCSocket:PumpMessages(UINTuEvent)CWinThread*pThread=AfxGetThread();while(bBlocking)file:/bBlocking/僅僅是一個標志,看用戶是否取消對Connect()的調用MSGmsg;if(PeekMessage(&msg,WM_SOCKET_NOTIFY)if(msg.message=WM_SOCKET_NOTIFY&WSAGETSELECTEVENT(msg.lParam)=uStopFlag)CAsyncSocket:DoCallBack(msg.wParam,msg.lParam);re

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論