跨线程使用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函数,然后在另外一个线程中调用其他成员函数。下面的例子就是一个典型的导致崩溃的过程:
其中Socket对象在主线程中被调用,在子线程中被关闭。
跟踪分析
这个问题的原因可以通过单步跟踪(F11)的方法来了解。我们在Socket.Create(0)处设断点,跟踪进去会发现下面的函数被调用:
在这个函数的开头,首先获得了一个pState的指针指向_afxSockThreadState对象。从名字可以看出,这似乎是一个和线程相关的变量,实际上它是一个宏,定义如下:
#define _afxSockThreadState AfxGetModuleThreadState()
我们没有必要去细究这个指针的定义是如何的,只要知道它是和当前线程密切关联的,其他线程应该也有类似的指针,只是指向不同的结构。
在这个函数中,CAsyncSocket创建了一个窗口,并把如下两个信息加入到pState所管理的结构中:
当调用Close时,我们再次跟踪,就会发现在KillSocket中,下面的函数出现错误:
我们在这个ASSERT处设置断点,跟踪进LookupHandle,会发现这个函数定义如下:
显然,这个函数试图从当前线程查询关于这个 socket的信息,可是这个信息放在创建这个socket的线程中,因此这种查询显然会失败,最终返回NULL。
如何在多线程之间传递socket??
有些特殊情况下,可能需要在不同线程之间传递socket。当然我不建议在使用CAsyncSOcket的时候这么做,因为这增加了出错的风险(尤其当出现拆解包问题时,有人称为粘包,我基本不认同这种称呼)。如果一定要这么做,方法应该是:
把当前线程的socket脱离(Detach)出来,在另一个线程中加载(Attach)进去 ,注意是SOCKET socket
上面的例子,我稍微做修改,就不会出错了: