Symptom
When you design a socket program using multiple threads, you will find that the program crashes when casyncsocket and Its Derived classes are used across threads. The so-called cross-thread means that the object calls the CREATE/attachhandle/Attach function in one thread and then calls other member functions in another thread. The following example shows a typical crash process:
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); }
The socket object is called in the main thread and closed in the Child thread.
Tracking and Analysis
The cause of this problem can be understood through the one-step tracking (F11) method. We set a breakpoint at socket. Create (0). When we trace it, we will find that the following function is called:
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); }
At the beginning of this function, we first obtain a pstate pointer pointing to the _ afxsockthreadstate object. It can be seen from the name that this seems to be a thread-related variable. In fact, it is a macro and is defined as follows:
#define _afxSockThreadState AfxGetModuleThreadState()
We do not need to elaborate on the definition of this pointer. As long as we know that it is closely related to the current thread, other threads should have similar pointers, but point to different structures.
In this function, casyncsocket creates a window and adds the following two information to the structure managed by 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);
When close is called, we trace it again and find that the following function has an error in killsocket:
void PASCAL CAsyncSocket::KillSocket(SOCKET hSocket, CAsyncSocket* pSocket) { ASSERT(CAsyncSocket::LookupHandle(hSocket, FALSE) != NULL);
When we set a breakpoint at this assert and track it into lookuphandle, we will find that the function is defined as follows:
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; }
Obviously, this function tries to query information about this socket from the current thread, but this information is stored in the thread that created this socket. Therefore, this query will obviously fail and finally return null.
Someone may ask, since it is an Assert error, it is okay if it is release. This is just self-deception. Assert/verify are the correct conditions for testing the normal operation of some programs. If assert fails, it may not appear in the release, but your program must run incorrectly and you will not know when an error occurs.
How to transmit socket between multiple threads
In some special cases, the socket may need to be passed between different threads. Of course, I do not recommend using casyncsocket because it increases the risk of errors (especially when the package is split, it is called a sticky package, I basically do not agree with this title ). If you must do this, the method should be:
- The thread that currently owns this socket calls the detach method, so that the socket handle is out of relationship with the c ++ object and the current thread.
- The current thread passes this object to another thread.
- Another thread creates a new casyncsocket object and calls attach
In the above example, if I make a slight modification, there will be no errors:
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); }