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);  
      

  • 相关阅读:
    job 定时任务的五种创建方式
    一步步实现 Redis 搜索引擎
    数据库第一二三范式
    MongoDB数组更新操作$addToSet和$each修饰符
    V8 执行 JavaScript 的过程
    servicebestpractice项目的更新
    公主连结过root检测-frida
    android使用AsyncHttpClient发送请求
    js检测dom元素的变化
    安卓手机关闭防火墙命令
  • 原文地址:https://www.cnblogs.com/cutepig/p/1507498.html
Copyright © 2011-2022 走看看