没画完的画

喂马 劈柴 BBQ~
posts - 37, comments - 55, trackbacks - 0, articles - 0
  C++博客 ::  :: 新随笔 :: 联系 :: 聚合  :: 管理

CAsyncSocket 的 OnReceive() 第一集

Posted on 2008-09-10 08:48 没画完的画 阅读(5017) 评论(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 3876long 1) line 530
CSocket::ProcessAuxQueue() line 
823
CSocketWnd::OnSocketNotify(unsigned 
int 3876long 1) line 1127
CWnd::OnWndMsg(unsigned 
int 883, unsigned int 3876long 1long * 0x0012e56c) line 1826 + 17 bytes
CWnd::WindowProc(unsigned 
int 883, unsigned int 3876long 1) line 1596 + 30 bytes
AfxCallWndProc(CWnd 
* 0x00374d20 {CSocketWnd hWnd=0x00110c92}, HWND__ * 0x00110c92, unsigned int 883, unsigned int 3876long 1) line 215 + 26 bytes
AfxWndProc(HWND__ 
* 0x00110c92, unsigned int 883, unsigned int 3876long 1) line 379
AfxWndProcBase(HWND__ 
* 0x00110c92, unsigned int 883, unsigned int 3876long 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 3876long 1) line 530
CSocket::ProcessAuxQueue() line 
823
CSocketWnd::OnSocketNotify(unsigned 
int 3876long 1) line 1127
CWnd::OnWndMsg(unsigned 
int 883, unsigned int 3876long 1long * 0x0012f074) line 1826 + 17 bytes
CWnd::WindowProc(unsigned 
int 883, unsigned int 3876long 1) line 1596 + 30 bytes
AfxCallWndProc(CWnd 
* 0x00374d20 {CSocketWnd hWnd=0x00110c92}, HWND__ * 0x00110c92, unsigned int 883, unsigned int 3876long 1) line 215 + 26 bytes
AfxWndProc(HWND__ 
* 0x00110c92, unsigned int 883, unsigned int 3876long 1) line 379
AfxWndProcBase(HWND__ 
* 0x00110c92, unsigned int 883, unsigned int 3876long 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 3876long 1) line 530
CSocket::ProcessAuxQueue() line 
823
CSocketWnd::OnSocketNotify(unsigned 
int 3876long 1) line 1127
CWnd::OnWndMsg(unsigned 
int 883, unsigned int 3876long 1long * 0x0012fb7c) line 1826 + 17 bytes
CWnd::WindowProc(unsigned 
int 883, unsigned int 3876long 1) line 1596 + 30 bytes
AfxCallWndProc(CWnd 
* 0x00374d20 {CSocketWnd hWnd=0x00110c92}, HWND__ * 0x00110c92, unsigned int 883, unsigned int 3876long 1) line 215 + 26 bytes
AfxWndProc(HWND__ 
* 0x00110c92, unsigned int 883, unsigned int 3876long 1) line 379
AfxWndProcBase(HWND__ 
* 0x00110c92, unsigned int 883, unsigned int 3876long 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__ * 0x00000000char * 0x00141f3dint 1) line 39 + 11 bytes
WinMain(HINSTANCE__ 
* 0x00400000, HINSTANCE__ * 0x00000000char * 0x00141f3dint 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上作提示,这个过程最好是异步操作,
除非是确定能迅速完成,才用同步。

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理