跨线程使用CSocket

CSocket断言错误:ASSERT(pState->m_hSocketWindow != NULL);

起因:在套接字处于连接或者发送状态时,试图关闭套接字,于是在这个断言语句处发生中断。

原因分析::

微软官方解释如下:http://support.microsoft.com/kb/140527/en-us

This assertion failure occurs because the CSocket object was either created or accepted in the context of another thread. The socket notification window was created in a different thread, and the m_hSocketWindow for the current thread is NULL, thus the assertion failure.

 明白了,就是跨线程使用CSocket,结果。。。。

解决方案:

As already mentioned, a CAsyncSocket object should be used only in the context of a single thread. If you need to switch the thread that is accessing a SOCKET connection with another thread, then you should use a separate CAsyncSocket object in each thread, and use the Detach and Attach functions to attach the CAsyncSocket object to the SOCKET handle in the thread that is about to use the socket. Use this sequence:

1.Use Detach() to detach the CAsyncSocket object from the SOCKET handle in the thread that is currently using the CAsyncSocket object.
2.Use Attach() to attach a different CAsyncSocket object to the SOCKET handle while in the context of the MFC UI thread in which you wish to begin accessing the SOCKET connection.

用多线程方法设计socket程序时,你会发现在跨线程使用CAsyncSocket及其派生类时,会出现程序崩溃。所谓跨线程,是指该对象在一个线程中调用Create/AttachHandle/Attach函数,然后在另外一个线程中调用其他成员函数。下面的例子就是一个典型的导致崩溃的过程:

 

[cpp]  view plain copy
  1. CAsyncSocket Socket;  
  2. UINT Thread(LPVOID)  
  3. {  
  4.        Socket.Close ();  
  5.        return 0;  
  6. }  
  7. void CTestSDlg::OnOK()   
  8. {  
  9.        // TODO: Add extra validation here  
  10.        Socket.Create(0);  
  11.        AfxBeginThread(Thread,0,0,0,0,0);  
  12. }  

其中Socket对象在主线程中被调用,在子线程中被关闭。

跟踪分析

这个问题的原因可以通过单步跟踪(F11)的方法来了解。我们在Socket.Create(0)处设断点,跟踪进去会发现下面的函数被调用:

[cpp]  view plain copy
  1. void PASCAL CAsyncSocket::AttachHandle(  
  2.           SOCKET hSocket, CAsyncSocket* pSocket, BOOL bDead)  
  3. {  
  4.     _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;  
  5.     BOOL bEnable = AfxEnableMemoryTracking(FALSE);  
  6.     if (!bDead)  
  7.     {  
  8.              ASSERT(CAsyncSocket::LookupHandle(hSocket, bDead) == NULL);  
  9.              if (pState->m_pmapSocketHandle->IsEmpty())  
  10.              {  
  11.                   ASSERT(pState->m_pmapDeadSockets->IsEmpty());  
  12.                   ASSERT(pState->m_hSocketWindow == NULL);  
  13.                   CSocketWnd* pWnd = new CSocketWnd;  
  14.                   pWnd->m_hWnd = NULL;  
  15.                   if (!pWnd->CreateEx(0, AfxRegisterWndClass(0),  
  16.                                    _T("Socket Notification Sink"),  
  17.                                  WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL))  
  18.                  {  
  19.                        TRACE0("Warning: unable to create socket notify window!/n");  
  20.                        AfxThrowResourceException();  
  21.                  }  
  22.                  ASSERT(pWnd->m_hWnd != NULL);  
  23.                  ASSERT(CWnd::FromHandlePermanent(pWnd->m_hWnd) == pWnd);  
  24.                  pState->m_hSocketWindow = pWnd->m_hWnd;  
  25.             }  
  26.             pState->m_pmapSocketHandle->SetAt((void*)hSocket, pSocket);  
  27.     }  
  28.     else  
  29.     {  
  30.            int nCount;  
  31.            if (pState->m_pmapDeadSockets->Lookup((void*)hSocket, (void*&)nCount))  
  32.                      nCount++;  
  33.            else  
  34.                      nCount = 1;  
  35.            pState->m_pmapDeadSockets->SetAt((void*)hSocket, (void*)nCount);  
  36.    }  
  37.    AfxEnableMemoryTracking(bEnable);  
  38. }  

在这个函数的开头,首先获得了一个pState的指针指向_afxSockThreadState对象。从名字可以看出,这似乎是一个和线程相关的变量,实际上它是一个宏,定义如下:

#define _afxSockThreadState AfxGetModuleThreadState()

我们没有必要去细究这个指针的定义是如何的,只要知道它是和当前线程密切关联的,其他线程应该也有类似的指针,只是指向不同的结构。

在这个函数中,CAsyncSocket创建了一个窗口,并把如下两个信息加入到pState所管理的结构中:

 
 
  1. pState->m_pmapSocketHandle->SetAt((void*)hSocket, pSocket);  
  2. pState->m_pmapDeadSockets->SetAt((void*)hSocket, (void*)nCount);  
  3. pState->m_hSocketWindow = pWnd->m_hWnd;  
  4. pState->m_pmapSocketHandle->SetAt((void*)hSocket, pSocket);  

当调用Close时,我们再次跟踪,就会发现在KillSocket中,下面的函数出现错误:

[cpp]  view plain copy
  1. void PASCAL CAsyncSocket::KillSocket(SOCKET hSocket, CAsyncSocket* pSocket)  
  2. {  
  3.         ASSERT(CAsyncSocket::LookupHandle(hSocket, FALSE) != NULL);  

我们在这个ASSERT处设置断点,跟踪进LookupHandle,会发现这个函数定义如下:

[cpp]  view plain copy
  1. CAsyncSocket* PASCAL CAsyncSocket::LookupHandle(SOCKET hSocket, BOOL bDead)  
  2. {  
  3.      CAsyncSocket* pSocket;  
  4.      _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;  
  5.      if (!bDead)  
  6.      {  
  7.              pSocket = (CAsyncSocket*)  
  8.              pState->m_pmapSocketHandle->GetValueAt((void*)hSocket);  
  9.              if (pSocket != NULL)  
  10.                   return pSocket;  
  11.     }  
  12.     else  
  13.     {  
  14.              pSocket = (CAsyncSocket*)  
  15.                   pState->m_pmapDeadSockets->GetValueAt((void*)hSocket);  
  16.              if (pSocket != NULL)  
  17.                    return pSocket;  
  18.     }  
  19.     return NULL;  
  20. }  

显然,这个函数试图从当前线程查询关于这个 socket的信息,可是这个信息放在创建这个socket的线程中,因此这种查询显然会失败,最终返回NULL。

如何在多线程之间传递socket??

有些特殊情况下,可能需要在不同线程之间传递socket。当然我不建议在使用CAsyncSOcket的时候这么做,因为这增加了出错的风险(尤其当出现拆解包问题时,有人称为粘包,我基本不认同这种称呼)。如果一定要这么做,方法应该是:

把当前线程的socket脱离(Detach)出来,在另一个线程中加载(Attach)进去 ,注意是SOCKET socket

上面的例子,我稍微做修改,就不会出错了:

[cpp]  view plain copy
  1. CAsyncSocket Socket;  
  2. UINT Thread(LPVOID sock)  
  3. {  
  4.          Socket.Attach((SOCKET)sock);//如果在线程外close(),记得在离开时Detach().  
  5.          Socket.Close ();  
  6.          return 0;  
  7. }  
  8. void CTestSDlg::OnOK()   
  9. {  
  10.          // TODO: Add extra validation here  
  11.          Socket.Create(0);  
  12.          SOCKET hSocket = Socket.Detach ();  
  13.          AfxBeginThread(Thread,(LPVOID)hSocket,0,0,0,0);  
  14. }  

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
线程在网络编程中经常使用,可以使程序能够同时处理多个客户端的请求,提高并发性能。在使用线程进行网络编程时,可以借助于C语言提供的csocket库来实现。 首先,需要创建一个主线程来监听客户端的连接请求,并接受连接。当接受到一个连接请求后,可以创建一个新的线程来处理该客户端的请求。这样,主线程可以继续监听其他客户端的连接请求。 在创建新线程时,可以使用C语言提供的pthread库来实现。通过创建新线程,可以在每个线程中处理一个客户端的请求。每个线程都可以使用独立的socket来与客户端进行通信。 需要注意的是,在多线程中,要确保对共享资源的访问是安全的。可以使用互斥锁(mutex)来保护共享资源,以防止多个线程同时修改同一个资源而导致数据不一致的问题。 下面是一个简单的示例代码,演示了如何使用线程进行网络编程: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include <sys/socket.h> #include <netinet/in.h> void *client_handler(void *arg) { int client_socket = *(int *)arg; // 处理客户端请求,具体逻辑在这里实现 // ... close(client_socket); pthread_exit(NULL); } int main() { int server_socket, client_socket; struct sockaddr_in server_addr, client_addr; socklen_t client_addr_len; pthread_t tid; // 创建套接字 server_socket = socket(AF_INET, SOCK_STREAM, 0); if (server_socket < 0) { perror("socket creation failed"); exit(EXIT_FAILURE); } // 设置服务器地址 server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8080); server_addr.sin_addr.s_addr = INADDR_ANY; // 绑定套接字到服务器地址 if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("bind failed"); exit(EXIT_FAILURE); } // 监听连接请求 if (listen(server_socket, 5) < 0) { perror("listen failed"); exit(EXIT_FAILURE); } printf("Server started, listening on port 8080...\n"); while (1) { // 接受连接请求 client_addr_len = sizeof(client_addr); client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_len); if (client_socket < 0) { perror("accept failed"); exit(EXIT_FAILURE); } printf("Accepted a new connection from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); // 创建新线程处理客户端请求 if (pthread_create(&tid, NULL, client_handler, &client_socket) < 0) { perror("pthread_create failed"); exit(EXIT_FAILURE); } } close(server_socket); return 0; } ``` 这段代码创建了一个服务器,使用线程处理客户端的连接请求。在`client_handler`函数中,可以编写具体的处理逻辑来处理客户端的请求。每个客户端连接都会创建一个新的线程来处理。 注意,以上代码只是一个简单示例,可能还需要根据具体需求进行修改和完善。同时,要确保在退出程序之前,关闭套接字和释放线程资源,以避免资源泄漏问题。 希望对你有所帮助!如果有任何问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值