posts - 495,comments - 227,trackbacks - 0

          現(xiàn)象

          用多線程方法設(shè)計(jì)socket程序時,你會發(fā)現(xiàn)在跨線程使用CAsyncSocket及其派生類時,會出現(xiàn)程序崩潰。所謂跨線程,是指該對象在一個線程中 調(diào)用Create/AttachHandle/Attach函數(shù),然后在另外一個線程中調(diào)用其他成員函數(shù)。下面的例子就是一個典型的導(dǎo)致崩潰的過程:
          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對象在主線程中被調(diào)用,在子線程中被關(guān)閉。

          跟蹤分析

          這個問題的原因可以通過單步跟蹤(F11)的方法來了解。我們在Socket.Create(0)處設(shè)斷點(diǎn),跟蹤進(jìn)去會發(fā)現(xiàn)下面的函數(shù)被調(diào)用:

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

          在這個函數(shù)的開頭,首先獲得了一個pState的指針指向_afxSockThreadState對象。從名字可以看出,這似乎是一個和線程相關(guān)的變量,實(shí)際上它是一個宏,定義如下:

          #define _afxSockThreadState AfxGetModuleThreadState()

          我們沒有必要去細(xì)究這個指針的定義是如何的,只要知道它是和當(dāng)前線程密切關(guān)聯(lián)的,其他線程應(yīng)該也有類似的指針,只是指向不同的結(jié)構(gòu)。

          在這個函數(shù)中,CAsyncSocket創(chuàng)建了一個窗口,并把如下兩個信息加入到pState所管理的結(jié)構(gòu)中:

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

          當(dāng)調(diào)用Close時,我們再次跟蹤,就會發(fā)現(xiàn)在KillSocket中,下面的函數(shù)出現(xiàn)錯誤:

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

          我們在這個ASSERT處設(shè)置斷點(diǎn),跟蹤進(jìn)LookupHandle,會發(fā)現(xiàn)這個函數(shù)定義如下:

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

          顯然,這個函數(shù)試圖從當(dāng)前線程查詢關(guān)于這個 socket的信息,可是這個信息放在創(chuàng)建這個socket的線程中,因此這種查詢顯然會失敗,最終返回NULL。

          有人會問,既然它是ASSERT出錯,是不是Release就沒問題了。這只是自欺欺人。ASSERT/VERIFY都是檢驗(yàn)一些程序正常運(yùn)行必須正確的條件。如果ASSERT都失敗,在Release中也許不會顯現(xiàn),但是你的程序肯定運(yùn)行不正確,啥時候出錯就不知道了。

          如何在多線程之間傳遞socket

          有些特殊情況下,可能需要在不同線程之間傳遞socket。當(dāng)然我不建議在使用CAsyncSOcket的時候這么做,因?yàn)檫@增加了出錯的風(fēng)險(xiǎn)(尤其當(dāng)出現(xiàn)拆解包問題時,有人稱為粘包,我基本不認(rèn)同這種稱呼)。如果一定要這么做,方法應(yīng)該是:

          1. 當(dāng)前擁有這個socket的線程調(diào)用Detach方法,這樣socket句柄和C++對象及當(dāng)前線程脫離關(guān)系
          2. 當(dāng)前線程把這個對象傳遞給另外一個線程
          3. 另外一個線程創(chuàng)建新的CAsyncSocket對象,并調(diào)用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 2008-07-03 15:22 SIMONE 閱讀(841) 評論(0)  編輯  收藏 所屬分類: C++
          主站蜘蛛池模板: 肃北| 镶黄旗| 临湘市| 额济纳旗| 桦川县| 久治县| 新泰市| 海淀区| 安仁县| 靖边县| 北京市| 阿合奇县| 田阳县| 桂平市| 扎囊县| 阿克苏市| 洛川县| 麟游县| 罗山县| 淮北市| 神池县| 库伦旗| 教育| 陇西县| 深圳市| 永嘉县| 峨山| 万州区| 牙克石市| 三亚市| 康保县| 古丈县| 阿鲁科尔沁旗| 蒲江县| 宝清县| 咸丰县| 米泉市| 丰宁| 芜湖市| 铜山县| 巢湖市|