|
Posted on 2008-09-10 08:48 没画完的画 阅读(5039) 评论(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上作提示,这个过程最好是异步操作, 除非是确定能迅速完成,才用同步。
|