zoukankan      html  css  js  c++  java
  • CAsyncSocket对象不能跨线程之分析 (转载)

    http://blog.vckbase.com/arong/archive/2005/12/03/15578.html

    现象

    用多线程方法设计socket程序时,你会发现在跨线程使用CAsyncSocket及其派生类时,会出现程序崩溃。所谓跨线程,是指该对象在一个线程中 调用Create/AttachHandle/Attach函数,然后在另外一个线程中调用其他成员函数。下面的例子就是一个典型的导致崩溃的过程:
    CAsyncSocket Socket;
    UINT Thread(LPVOID)
    {
    Socket.Close ();
    return 0;
    }
    void CTestSDlg::OnOK()
    {
    // TODO: Add extra validation here
    Socket.Create(0);
    AfxBeginThread(Thread,0,0,0,0,0);
    }

    其中Socket对象在主线程中被调用,在子线程中被关闭。

    跟踪分析

    这个问题的原因可以通过单步跟踪(F11)的方法来了解。我们在Socket.Create(0)处设断点,跟踪进去会发现下面的函数被调用:

    void PASCAL CAsyncSocket::AttachHandle(
    SOCKET hSocket, CAsyncSocket* pSocket, BOOL bDead)
    {
    _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
    BOOL bEnable = AfxEnableMemoryTracking(FALSE);
    if (!bDead)
    {
    ASSERT(CAsyncSocket::LookupHandle(hSocket, bDead) == NULL);
    if (pState->m_pmapSocketHandle->IsEmpty())
    {
    ASSERT(pState->m_pmapDeadSockets->IsEmpty());
    ASSERT(pState->m_hSocketWindow == NULL);
    CSocketWnd* pWnd = new CSocketWnd;
    pWnd->m_hWnd = NULL;
    if (!pWnd->CreateEx(0, AfxRegisterWndClass(0),
    _T("Socket Notification Sink"),
    WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL))
    {
    TRACE0("Warning: unable to create socket notify window!\n");
    AfxThrowResourceException();
    }
    ASSERT(pWnd->m_hWnd != NULL);
    ASSERT(CWnd::FromHandlePermanent(pWnd->m_hWnd) == pWnd);
    pState->m_hSocketWindow = pWnd->m_hWnd;
    }
    pState->m_pmapSocketHandle->SetAt((void*)hSocket, pSocket);
    }
    else
    {
    int nCount;
    if (pState->m_pmapDeadSockets->Lookup((void*)hSocket, (void*&)nCount))
    nCount++;
    else
    nCount = 1;
    pState->m_pmapDeadSockets->SetAt((void*)hSocket, (void*)nCount);
    }
    AfxEnableMemoryTracking(bEnable);
    }

    在这个函数的开头,首先获得了一个pState的指针指向_afxSockThreadState对象。从名字可以看出,这似乎是一个和线程相关的变量,实际上它是一个宏,定义如下:

    #define _afxSockThreadState AfxGetModuleThreadState()

    我们没有必要去细究这个指针的定义是如何的,只要知道它是和当前线程密切关联的,其他线程应该也有类似的指针,只是指向不同的结构。

    在这个函数中,CAsyncSocket创建了一个窗口,并把如下两个信息加入到pState所管理的结构中:

        pState->m_pmapSocketHandle->SetAt((void*)hSocket, pSocket);
    pState->m_pmapDeadSockets->SetAt((void*)hSocket, (void*)nCount);
    pState->m_hSocketWindow = pWnd->m_hWnd;
    pState->m_pmapSocketHandle->SetAt((void*)hSocket, pSocket);

    当调用Close时,我们再次跟踪,就会发现在KillSocket中,下面的函数出现错误:

        void PASCAL CAsyncSocket::KillSocket(SOCKET hSocket, CAsyncSocket* pSocket)
    {
    ASSERT(CAsyncSocket::LookupHandle(hSocket, FALSE) != NULL);

    我们在这个ASSERT处设置断点,跟踪进LookupHandle,会发现这个函数定义如下:

    CAsyncSocket* PASCAL CAsyncSocket::LookupHandle(SOCKET hSocket, BOOL bDead)
    {
    CAsyncSocket* pSocket;
    _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
    if (!bDead)
    {
    pSocket = (CAsyncSocket*)
    pState->m_pmapSocketHandle->GetValueAt((void*)hSocket);
    if (pSocket != NULL)
    return pSocket;
    }
    else
    {
    pSocket = (CAsyncSocket*)
    pState->m_pmapDeadSockets->GetValueAt((void*)hSocket);
    if (pSocket != NULL)
    return pSocket;
    }
    return NULL;
    }

    显然,这个函数试图从当前线程查询关于这个 socket的信息,可是这个信息放在创建这个socket的线程中,因此这种查询显然会失败,最终返回NULL。

    有人会问,既然它是ASSERT出错,是不是Release就没问题了。这只是自欺欺人。ASSERT/VERIFY都是检验一些程序正常运行必须正确的条件。如果ASSERT都失败,在Release中也许不会显现,但是你的程序肯定运行不正确,啥时候出错就不知道了。

    如何在多线程之间传递socket

    有些特殊情况下,可能需要在不同线程之间传递socket。当然我不建议在使用CAsyncSOcket的时候这么做,因为这增加了出错的风险(尤其当出现拆解包问题时,有人称为粘包,我基本不认同这种称呼)。如果一定要这么做,方法应该是:

    1. 当前拥有这个socket的线程调用Detach方法,这样socket句柄和C++对象及当前线程脱离关系
    2. 当前线程把这个对象传递给另外一个线程
    3. 另外一个线程创建新的CAsyncSocket对象,并调用Attach

    上面的例子,我稍微做修改,就不会出错了:

    CAsyncSocket Socket;
    UINT Thread(LPVOID sock)
    {
    Socket.Attach((SOCKET)sock);
    Socket.Close ();
    return 0;
    }
    void CTestSDlg::OnOK()
    {
    // TODO: Add extra validation here
    Socket.Create(0);
    SOCKET hSocket = Socket.Detach ();
    AfxBeginThread(Thread,(LPVOID)hSocket,0,0,0,0);
    }
    posted on 2005-12-03 14:08 馨荣家园 阅读(6771) 评论(15)  编辑 收藏

    评论

    # re: CAsyncSocket对象不能跨线程之分析 2006-02-15 15:42 ocean
    很好!

    # re: CAsyncSocket对象不能跨线程之分析 2006-03-02 22:55 babazhang
    MSDN里给出的是一句不痛不痒的话,CSocket /CAsyncSocket is not thread-safety.  说白了就是数据一旦 跟窗口搭上钩后,消息循化就会和子线程有数据竞争,而socket缓冲区是没有采用同步对象保护的,所以就会不安全。

    ------------分割线-----------------

     CSocket多线程

    //公有变量  
      CSocket   ServerSocket;  
      CSocket   ClientSocket;  
      //主程序  
      {  
      ServerSocket.Create(m_Port);//创建监听端口  
      ServerSocket.Listen(-1);  
      while(true)  
      {  
      ServerSocket.Accept(ClientSocket);  
      clientCount+=1;  
      SOCKET   hSocket=NULL;  
      hSocket = ClientSocket.Detach(); //获得句柄  
      if(ClientSocket   !=NULL   &&   clientCount<=Maxnum)//连接用户未超过限制  
      {  
      AfxBeginThread(Listened,(LPVOID)hSocket,THREAD_PRIORITY_NORMAL); //启动线程  
      }  
      else  
      {  
      ....  
      }  
      }  
       
       
      线程:  
     UINT   Listened   (LPVOID   pParam)  
      {  
      SOCKET   sock   =(SOCKET)pParam;  
      CSocket   sockRecv;  
      sockRecv.Attach(sock);  
      .......  
      return   0;  
      }  
      s
        
        
    设置connet超时
    int   ioctlsocket(  
          SOCKET   s,                    
          long   cmd,                    
          u_long   FAR   *argp      
      );  
       
      cmd   =   FIONBIO   ;  
      argp   =   10000;
     
      设置发送超时
      /set   time   out  
      int   TimeOut=6000;    
      if(::setsockopt(cClient,SOL_SOCKET,SO_SNDTIMEO,(char   *)&TimeOut,sizeof(TimeOut))==SOCKET_ERROR){  
      return   0;  
      }
      下面这个什么意思??
      select(sock,   &Socket_Read,(fd_set   *)   0,(fd_set   *)   0,&timeout);  
      

  • 相关阅读:
    Windows XP下 Android开发环境 搭建
    Android程序的入口点
    在eclipse里 新建android项目时 提示找不到proguard.cfg
    64位WIN7系统 下 搭建Android开发环境
    在eclipse里 新建android项目时 提示找不到proguard.cfg
    This Android SDK requires Android Developer Toolkit version 20.0.0 or above
    This Android SDK requires Android Developer Toolkit version 20.0.0 or above
    Android requires compiler compliance level 5.0 or 6.0. Found '1.4' instead
    Windows XP下 Android开发环境 搭建
    Android程序的入口点
  • 原文地址:https://www.cnblogs.com/cutepig/p/1507498.html
Copyright © 2011-2022 走看看