CAsyncSocketがマルチスレッドに遭遇すると

4938 ワード

げんしょう
マルチスレッドメソッドでsocketプログラムを設計すると、CAsyncSocketとその派生クラスをスレッド間で使用すると、プログラムがクラッシュすることがわかります.クロススレッドとは、オブジェクトが1つのスレッドで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オブジェクトはメインスレッドで呼び出され、サブスレッドで閉じられます.
トラッキング解析
この問題の原因は,単一ステップ追跡(F 11)法によって理解できる.私たちは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が管理する構造に次の2つの情報を追加します.
    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を使用する場合はお勧めしません.これはエラーのリスクを増加させるためです(特に、分解パッケージの問題が発生した場合、粘着パッケージと呼ばれる人がいますが、私は基本的にこの呼び方を認めません).もしそうしなければならないなら、方法は次のとおりです.
  • 現在このsocketを持つスレッドはDetachメソッドを呼び出し、このようにsocketハンドルとC++オブジェクトと現在のスレッドとの関係が
  • から離れる.
  • 現在のスレッドは、このオブジェクトを別のスレッド
  • に渡す.
  • 別のスレッドは、新しい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);
    }