|
Posted on 2008-09-10 08:48 沒畫完的畫 閱讀(5050) 評論(3) 編輯 收藏 引用 所屬分類: VC
在應用中,Client端 使用了 MFC 的 CAsyncSocket 跟 Server 通信 Client Server CAsyncSocket::Send() --> | CAsyncSocket::OnReceive() <-- | Client 使用 Send() 向 Server 端發送報文,Sever 端返回報文時,會觸發 OnReceive() 事件,告訴我們有數據到達了 接收 Server 數據包的實現代碼如下:
class CClientSocket : public CAsyncSocket
  {
private:
CBytesBuff m_buff; // CBytesBuff 封裝了緩沖區的操作
};
#define RECV_BUFFER_SIZE 1024
void CClientSocket::OnReceive(int nErrorCode)
  {
 char szBuff[RECV_BUFFER_SIZE] = {0}; // 接收數據的緩沖區
int nRead = 0; // 真正接收數據的大小
nRead = Receive(szBuff, sizeof(szBuff) ); // 接收數據
switch (nRead)
 {
case 0: // 接收數據的長度返回0, 表示 Socket 已經斷開
 {
Close();
Notify_UI_SOCKET_CLOSE( ::WSAGetLastError() );
};
break;
case SOCKET_ERROR: // 出錯
 {
int nErrCode = ::WSAGetLastError();
if ( nErrCode != WSAEWOULDBLOCK)
 {
Close();
Notify_UI_SOCKET_CLOSE( nErrCode );
}
}
break;
default: // 正常情況
 {
m_buff.append(szBuff, nRead);
while( m_buff.getDataSize() <= 0 )
 {
// handleData() 返回已經處理的數據長度, 然后從 m_buff 中移刪已經處理的數據
// 直至 m_buff 中沒有數據
// handleData() 函數只有處理完
int nHandledLen = handleData(m_buff.getData(), m_buff.getSize());
m_buff.popData(nHandledLen);
}
}

}// switch

CAsyncSocket::OnReceive(nErrorCode);
}


我總以為上段的代碼可以正常地運行,但事與愿違 當 Client端在處理 Server端 數據時,有時在 handleData() 里調用了一個 MessageBox() 時,就會連續彈出兩個 MessageBox 經過檢查, 在 handleData() 中處理某一個報文時調用 MessageBox() 時,如果 MessageBox() 不返回,那么 handleData() 自然也不會返回 這里后面的 m_buff.popData(nHandledLen); 語句也無法執行到, 那么數據永遠在 m_buff 里無法移刪除, 如果這時 Server端 又再返回一個 數據包 , CAsyncSocket 又會觸發 OnReceive(),{請記住,這時上一個 數據包 還在 m_buff 里} 所以 當執行到 int nHandledLen = handleData(m_buff.getData(), m_buff.getSize()); 時 又會彈出一個 MessageBox(); 導致的異常有 1、調用 MessageBox() 的那段處理 數據包的 代碼會被執行兩次 2、m_buff.popData(nHandledLen); 這句代碼同樣會執行兩次導致數據解析出錯
在我的應用中,只允許處理完一個 數據包后才能處理下一個 數據包
火星人都知道,當有數據到達時,CAsynSocket 是采用消息的方式來通知的
BOOL CAsyncSocket::AsyncSelect(long lEvent)
  {
ASSERT(m_hSocket != INVALID_SOCKET);

_AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
ASSERT(pState->m_hSocketWindow != NULL);

return WSAAsyncSelect(m_hSocket, pState->m_hSocketWindow,
WM_SOCKET_NOTIFY, lEvent) != SOCKET_ERROR;
}

從 CAsyncSocket 的實現來看,每一條線程 CAsyncSocket 都會創建一個{隱形窗口},當有網絡事件時, 會向這個窗口發送 WM_SOCKET_NOTIFY 消息
BEGIN_MESSAGE_MAP(CSocketWnd, CWnd)
//{{AFX_MSG_MAP(CWnd)
ON_MESSAGE(WM_SOCKET_NOTIFY, OnSocketNotify)
ON_MESSAGE(WM_SOCKET_DEAD, OnSocketDead)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
LRESULT CSocketWnd::OnSocketNotify(WPARAM wParam, LPARAM lParam)
  {
CSocket::AuxQueueAdd(WM_SOCKET_NOTIFY, wParam, lParam);
CSocket::ProcessAuxQueue();
return 0L;
}

請注意:上面的 CSocket 并非是 class CSocket : public CAsyncSocket, 而是另一個在MFC框架內部的一個封裝.
結論是: CAsyncSocket::OnConnect(), CAsyncSocket::OnRecive()...... 這些函數是在 WM_SOCKET_NOTIFY 的消息響應 函數里調用的
Google到的東西 AfxMessageBox()、MessageBox() 并不會阻塞消息隊列
具體為什么 AfxMessageBox()、MessageBox() 不會阻塞到消息隊列,需要再詳細查找相關資料 -- AfxMessageBox()、MessageBox() 會造成代碼的阻塞(因為如果 MessageBox() 不返回,是不會執行到 MessageBox() 之后的代碼) 但它們并不會造成線程消息隊列的阻塞 Google 到一些資料說的是,AfxMessageBox() 和 MessageBox() 會把主窗體Enable 并且 會有另一個消息循環, 雖然原來窗體的消息循環的 DispatchMessage() 沒有返回,但由于 AfxMessageBox() 和 MessageBox() 又有另一個消息循環 所以,新的消息又會被響應到
作了一個簡單的試驗, 寫一個 Client 和 一個 Server Client 在收到 Server 端的數據時就顯示一個 MessageBox(),Server 向 Client 端發送兩次數據 Client 端在第二次收到數據時,設置斷點,查看堆棧
CTestMFCSocketClientDlg::show() line 178
CClientSocket::OnReceive(int 0) line 61
CAsyncSocket::DoCallBack(unsigned int 3876, long 1) line 530
CSocket::ProcessAuxQueue() line 823
CSocketWnd::OnSocketNotify(unsigned int 3876, long 1) line 1127
CWnd::OnWndMsg(unsigned int 883, unsigned int 3876, long 1, long * 0x0012e56c) line 1826 + 17 bytes
CWnd::WindowProc(unsigned int 883, unsigned int 3876, long 1) line 1596 + 30 bytes
 AfxCallWndProc(CWnd * 0x00374d20 {CSocketWnd hWnd=0x00110c92}, HWND__ * 0x00110c92, unsigned int 883, unsigned int 3876, long 1) line 215 + 26 bytes
AfxWndProc(HWND__ * 0x00110c92, unsigned int 883, unsigned int 3876, long 1) line 379
AfxWndProcBase(HWND__ * 0x00110c92, unsigned int 883, unsigned int 3876, long 1) line 220 + 21 bytes
USER32! 77d48734()
USER32! 77d48816()
USER32! 77d489cd()
USER32! 77d48a10()
USER32! 77d5e2b9()
USER32! 77d561c6()
USER32! 77d6a92e()
USER32! 77d6a294()
USER32! 77d95fbb()
USER32! 77d96060()
USER32! 77d80577()
USER32! 77d8052f()
CWinApp::DoMessageBox(const char * 0x004153e4 `string', unsigned int 48, unsigned int 0) line 113 + 25 bytes
AfxMessageBox(const char * 0x004153e4 `string', unsigned int 0, unsigned int 0) line 131 + 26 bytes
CTestMFCSocketClientDlg::show() line 179
CClientSocket::OnReceive(int 0) line 61
CAsyncSocket::DoCallBack(unsigned int 3876, long 1) line 530
CSocket::ProcessAuxQueue() line 823
CSocketWnd::OnSocketNotify(unsigned int 3876, long 1) line 1127
CWnd::OnWndMsg(unsigned int 883, unsigned int 3876, long 1, long * 0x0012f074) line 1826 + 17 bytes
CWnd::WindowProc(unsigned int 883, unsigned int 3876, long 1) line 1596 + 30 bytes
 AfxCallWndProc(CWnd * 0x00374d20 {CSocketWnd hWnd=0x00110c92}, HWND__ * 0x00110c92, unsigned int 883, unsigned int 3876, long 1) line 215 + 26 bytes
AfxWndProc(HWND__ * 0x00110c92, unsigned int 883, unsigned int 3876, long 1) line 379
AfxWndProcBase(HWND__ * 0x00110c92, unsigned int 883, unsigned int 3876, long 1) line 220 + 21 bytes
USER32! 77d48734()
USER32! 77d48816()
USER32! 77d489cd()
USER32! 77d48a10()
USER32! 77d5e2b9()
USER32! 77d561c6()
USER32! 77d6a92e()
USER32! 77d6a294()
USER32! 77d95fbb()
USER32! 77d96060()
USER32! 77d80577()
USER32! 77d8052f()
CWinApp::DoMessageBox(const char * 0x004153e4 `string', unsigned int 48, unsigned int 0) line 113 + 25 bytes
AfxMessageBox(const char * 0x004153e4 `string', unsigned int 0, unsigned int 0) line 131 + 26 bytes
CTestMFCSocketClientDlg::show() line 179
CClientSocket::OnReceive(int 0) line 61
CAsyncSocket::DoCallBack(unsigned int 3876, long 1) line 530
CSocket::ProcessAuxQueue() line 823
CSocketWnd::OnSocketNotify(unsigned int 3876, long 1) line 1127
CWnd::OnWndMsg(unsigned int 883, unsigned int 3876, long 1, long * 0x0012fb7c) line 1826 + 17 bytes
CWnd::WindowProc(unsigned int 883, unsigned int 3876, long 1) line 1596 + 30 bytes
 AfxCallWndProc(CWnd * 0x00374d20 {CSocketWnd hWnd=0x00110c92}, HWND__ * 0x00110c92, unsigned int 883, unsigned int 3876, long 1) line 215 + 26 bytes
AfxWndProc(HWND__ * 0x00110c92, unsigned int 883, unsigned int 3876, long 1) line 379
AfxWndProcBase(HWND__ * 0x00110c92, unsigned int 883, unsigned int 3876, long 1) line 220 + 21 bytes
USER32! 77d48734()
USER32! 77d48816()
USER32! 77d489cd()
USER32! 77d496c7()
CWinThread::PumpMessage() line 853
CWnd::RunModalLoop(unsigned long 4) line 3489 + 19 bytes
CDialog::DoModal() line 539 + 12 bytes
CTestMFCSocketClientApp::InitInstance() line 65 + 8 bytes
AfxWinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00141f3d, int 1) line 39 + 11 bytes
WinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00141f3d, int 1) line 30
WinMainCRTStartup() line 330 + 54 bytes
KERNEL32! 7c816fd7()

從堆棧的情況來看,AfxMessageBox() 里面確實存在另一個消息循環在處理消息。 但還未從 Microsoft 的MSDN上找到相關的官方資料來證實這一點。 未解決
所以一切清楚了,原因是即使 AfxMessageBox() 不返回,也不會影響到 OnRecive() 的調用
目前想到的解決方法有兩個,大概思路如下 第一種:想辦法在{第二個消息循環}中過濾掉 CAsynSocket 的 WM_SOCKET_NOTIFY 消息,并將這些過濾掉的消息存放在一個隊列,等{第二個消息循環}結束后,再把剛剛保存在隊列里的消息放回消息隊列里,讓第一個消息循環去處理
第二種:在 CAsynSocket 的 OnRecive() 里開線程,然后通過線程鎖來保證,在未處理完{Server端發送過來的報文}時,如果Server端有新的報文過來,會阻塞在線程里
Feedback
# re: CAsyncSocket 的 OnReceive()[未登錄] 回復 更多評論
2008-09-10 19:53 by
用UI消息提示錯誤正確,但不要用彈出式窗口。
# re: CAsyncSocket 的 OnReceive() 回復 更多評論
2008-09-10 22:13 by
@漂舟
用UI消息提示錯誤正確,但不要用彈出式窗口。
樓上,你說的“用UI消息提示錯誤正確”是什么意思?
# re: CAsyncSocket 的 OnReceive()[未登錄] 回復 更多評論
2008-09-10 22:44 by
事務失敗的消息,用戶有可能需要知道, 所以必須在UI上有所提示、或顯示,或暫時放入隊列,用戶查看時,可顯示, 即這個思路我認為是相當正確的, 在UI上作提示,這個過程最好是異步操作, 除非是確定能迅速完成,才用同步。
|