weidagang2046的專欄

          物格而后知致
          隨筆 - 8, 文章 - 409, 評論 - 101, 引用 - 0
          數據加載中……

          CAsyncSocket對象不能跨線程之分析

          現象

          用多線程方法設計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);
          }
          


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

          posted on 2006-11-07 20:07 weidagang2046 閱讀(487) 評論(0)  編輯  收藏 所屬分類: Windows

          主站蜘蛛池模板: 晋江市| 龙海市| 敦煌市| 宽城| 沾益县| 咸宁市| 库伦旗| 新竹县| 宁乡县| 嘉峪关市| 怀化市| 乌兰县| 永登县| 双辽市| 扎赉特旗| 宁国市| 昔阳县| 当涂县| 杭州市| 河源市| 台山市| 开封县| 禹州市| 肥西县| 乌兰察布市| 馆陶县| 尖扎县| 新乡市| 甘德县| 靖江市| 安新县| 山东省| 浦江县| 疏附县| 宜丰县| 渝中区| 辛集市| 伊春市| 贵定县| 稷山县| 苏尼特左旗|