Socket API,CAsyncSocket,CSocket內幕及其用法 (轉)
Socket有同步阻塞方式和非同步非阻塞方式兩種使用,事實上同步和非同步在我們的生涯中可能遇到了很多,而Socket也沒什麼特別。雖然同步好用,不費勁,但不能滿足一些應用場合,其也很低。
也許初涉程式設計的人不能理解“同步(或阻塞)”和“非同步(或非阻塞)”,其實簡單兩句話就能講清楚,同步和非同步往往都是針對一個來說的,“同步”就是函式直到其要的功能全部完成時才返回,而“非同步”則是,函式僅僅做一些簡單的工作,然後馬上返回,而它所要實現的功能留給別的執行緒或者函式去完成。例如,SendMessage就是“同步”函式,它不但傳送訊息到訊息佇列,還需要等待訊息被執行完才返回;相反PostMessage就是個非同步函式,它只管傳送一個訊息,而不管這個訊息是否被處理,就馬上返回。
一、Socket
首先應該知道,有Socket1.1提供的原始API函式,和Socket2.0提供的一組擴充套件函式,兩套函式。這兩套函式有重複,但是2.0提供的函式功能更強大,函式數量也更多。這兩套函式可以靈活混用,分別包含在頭Winsock.h,Winsock2.h,分別需要引入庫wsock32.lib、Ws2_32.lib。
1、預設用作同步阻塞方式,那就是當你從不WSAIoctl()和ioctlsocket()來改變Socket IO,也從不呼叫WSAAsync()和WSAEventSelect()來選擇需要處理的Socket事件。正是由於函式accept(),WSAAccept(),connect(),WSAConnect(),send(),Wend(),recv(),WSARecv()等函式被用作阻塞方式,所以可能你需要放在專門的執行緒裡,這樣以不影響主的執行和主視窗的重新整理。
2、如果作為非同步用,那麼程式主要就是要處理事件。它有兩種處理事件的辦法:
第一種,它常關聯一個視窗,也就是非同步Socket的事件將作為訊息發往該視窗,這是由WinSock擴充套件規範裡的一個函式WSAAsyncSelect()來實現和視窗關聯。最終你只需要處理視窗訊息,來收發資料。
第二種,用到了擴充套件規範裡另一個關於事件的函式WSAEventSelect(),它是用事件的方式來處理Socket事件,也就是,你必須首先用WSACreateEvent()來建立一個事件物件,然後呼叫WSAEventSelect()來使得Socket的事件和這個事件物件關聯。最終你將要在一個執行緒裡用WSAWaitForMultipleEvents()來等待這個事件物件被觸發。這個過程也稍顯複雜。
二、CAsyncSocket
看類名就知道,它是一個非同步非阻塞Socket封裝類,CAsyncSocket::Create()有一個引數指明瞭你想要處理哪些Socket事件,你關心的事件被指定以後,這個Socket預設就被用作了非同步方式。那麼CAsyncSocket內部到底是如何將事件交給你的呢?
CAsyncSocket的Create()函式,除了建立了一個SOCKET以外,還建立了個CSocketWnd視窗物件,並使用WSAAsyncSelect()將這個SOCKET與該視窗物件關聯,以讓該視窗物件處理來自Socket的事件(訊息),然而CSocketWnd收到Socket事件之後,只是簡單地回撥CAsyncSocket::OnReceive(),CAsyncSocket::OnSend(),CAsyncSocket::OnAccept(),CAsyncSocket::OnConnect()等虛擬函式。所以CAsyncSocket的派生類,只需要在這些虛擬函式里新增傳送和接收的程式碼。
簡化後,大致的程式碼為:
bool CAsyncSocket::Create( long lEvent ) 數lEvent是指定你所關心的Socket事件
{
m_hSocket = socket( PF_, SOCK_STREAM, 0 ); 建Socket本身
CSocketWnd* pSockWnd = new CSocketWnd; 建響應事件的視窗,實際的這個視窗在AfxSockInit()呼叫時就被建立了。
pSockWnd->Create(...);
WSAAsyncSelect( m_hSocket, pSockWnd->m_hWnd, WM_SOCKET_NOTIFY, lEvent ); 事件和視窗關聯
}
static void PASCAL CAsyncSocket::DoCallBack(WPARAM wParam, LPARAM lParam)
{
CAsyncSocket Socket;
Socket.Attach( (SOCKET)wParam ); 就是觸發這個事件的Socket的控制程式碼
int nErrorCode = WSAGETSELECTERROR(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 ); 到Socket事件訊息,回撥CAsyncSocket的DoCallBack()函式
return 0L;
}
然而,最不容易被初學Socket程式設計的人理解的,也是本文最要提醒的一點是,客戶方在使用CAsyncSocket::Connect()時,往往返回一個WSAEWOULLOCK的錯誤(其它的某些函式呼叫也如此),實際上這不應該算作一個錯誤,它是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 ) 於非同步操作需要時間,不能立即完成,所以Socket返回這個錯誤
{
入訊息迴圈,以從執行緒訊息佇列裡檢視FD_CONNECT訊息,直到收到FD_CONNECT訊息,認為連線成功。
while( PumpMessages( FD_CONNECT ) );
}
}
}
BOOL CSocket::PumpMessages( UINT uEvent )
{
CWinThread* pThread = AfxGetThread();
while( 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(); 理訊息佇列裡的其它訊息
pThread->OnIdle(-1);
}
}
}
BOOL CSocket::OnMessagePending()
{
MSG msg;
if( PeekMessage( &msg, NULL, WM_PAINT, WM_PAINT, PM_REMOVE ) )
{ 裡僅關心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連線上序列化資料。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-977228/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- CSocket設定超時(轉)
- 螢幕取詞核心內幕 (轉)
- DDX/DDV工作內幕 (轉)
- MFC下CSocket程式設計詳解(轉)程式設計
- Nimda程式設計內幕 (轉)程式設計
- Linux socket APILinuxAPI
- Linux開機程式內幕(轉)Linux
- MFC技術內幕簡結 (轉)
- socket基本的API使用API
- systemctl用法及其語法
- Top 5 字型及其用法
- CAsyncSocket TCP協議通訊速度慢的問題(轉)TCP協議
- Linux 核心偵錯程式內幕(轉)Linux
- 利用回撥函式和CSocket建立網路程式 (轉)函式
- 轉移資料夾及其裡面所有內容
- Lotus CGI 變數表及其用法變數
- 揭秘Linux核心偵錯程式之內幕(轉)Linux
- Git 內幕(一)Git
- LangChain內幕指南LangChain
- Blender參考API用法API
- Elasticsearch cat api的用法ElasticsearchAPI
- ElasticSearch之基本用法APIElasticsearchAPI
- CSS 塊級元素和行內元素和行內塊元素 及其相互轉換CSS
- 玩轉 PHP 網路程式設計全套之 socket 選項設定 APIPHP程式設計API
- MFC下CSocket程式設計詳解程式設計
- WebKit技術內幕WebKit
- 理解socket.io(一)---相關的APIAPI
- 徹底解決關於CSocket類的Receive超時的問題(轉)
- Object Pascal中String型別的內幕探討 (轉)Object型別
- 軟體相關技術及其在國內的應用 (轉)
- BSD socket入門(轉)
- ECMAScript5.1及其新增API簡介API
- 初窺c++11:lambda函式及其用法C++函式
- 揭祕JAVA JVM內幕JavaJVM
- zendframeworker命名規則內幕Framework
- Slackware Linux技術內幕之--包管理機制(轉)Linux
- 深入理解Linux網路內幕-PART I -通用背景(轉)Linux
- Hibernate的基本API的用法API