在最近修改代码时发现,如果使用了CSocket(CAsyncSocket)对象进行网络通信,在程序结束时关闭这个socket时程序就会崩溃。之前代码是好的,改出来的问题。对比代码和在网上找了些资料,确认CSocket(CAsyncSocket)对象在多线程使用时有些要注意的地方,这里稍微总结一下。简单来说,如果在线程A中创建了CSocket(CAsyncSocket)对象,如果在其他线程中直接调用Close()方法关闭它,程序就会崩溃。如果需要在其他线程中关闭,需要做一下事情:

一.在创建Socket的线程中分离socket对象和当前线程的关联。

1     g_pMySocket = new CMySocket; //CMySocket继承CSocket或CAsyncSocket。
2 if (g_pMySocket && g_pMySocket->Create() == false)
3 {
4 MessageBox(_T("Create mysocket fail!"));
5 }
6 g_pMySocket->m_socket = g_pMySocket->Detach(); //m_socket为自定义成员变量。

二.在需要关闭的线程中再关联socket对象。

1     g_pMySocket->Attach(g_pMySocket->m_socket);
2 //Do sth...
3 g_pMySocket->Close(); //

修改起来很简单,主要分析一下为何要这样做.

CSocket继承自CAsyncSocket,相关代码都差不多。首先在创建socket时会调用CAsyncSocket的create方法。create方法中调用CAsyncSocket::Socket的构造函数,在构造函数中会调用CAsyncSocket::AttachHandle,这是个静态函数。主要就是在这个函数中会将当前线程和socket对象绑定,代码如下:

 1 void PASCAL CAsyncSocket::AttachHandle(SOCKET hSocket, CAsyncSocket* pSocket, BOOL bDead)
2 {
3 /*
4 * _afxSockThreadState 其实就是 AfxGetModuleThreadState(),它会返回当前线程的一个 AFX_MODULE_THREAD_STATE 对象, AFX_MODULE_THREAD_STATE
5 * 是一个继承自 CNoTrackObject 的类,它里面有以下几个public的成员变量和socket有关:
6 * // WinSock specific thread state
7 * HWND m_hSocketWindow; //WinSock必须有一个窗口用于接收相关消息,这是个隐藏窗口,后面会看到。
8 * #ifdef _AFXDLL
9 * CEmbeddedButActsLikePtr<CMapPtrToPtr> m_pmapSocketHandle; //当前子类(自己写的)socket对象和m_hSocket的map
10 * CEmbeddedButActsLikePtr<CMapPtrToPtr> m_pmapDeadSockets; //当socket对象需要关闭时会把这个socket加到这个map中
11 * CEmbeddedButActsLikePtr<CPtrList> m_plistSocketNotifications; //存储当前socket的相关消息,如 WM_SOCKET_NOTIFY, WM_SOCKET_DEAD, PM_REMOVE
12 * #else
13 * CMapPtrToPtr* m_pmapSocketHandle;
14 * CMapPtrToPtr* m_pmapDeadSockets;
15 * CPtrList* m_plistSocketNotifications;
16 * CAsyncSocket中有很多方法都是静态的,都是通过这个 _AFX_SOCK_THREAD_STATE 对象找到子类实例化的socket对象。
17 */
18 _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
19
20 BOOL bEnable = AfxEnableMemoryTracking(FALSE);
21
22 TRY
23 {
24 if (!bDead) //默认bDead是false,走这个流程,在killsocket时bDead会为true
25 {
26 /*
27 * 默认当前线程是没有wincoket对象的,所以先 ASSERT 一下,如果关闭soket是只调用 closesoket 方法,而不是使用
28 * Close()方法,这个ASSERT就会报错,因为 closesoket 只是关闭了socket,而它和线程的关联还没有去掉。
29 */
30 ASSERT(CAsyncSocket::LookupHandle(hSocket, bDead) == NULL);
31
32 if (pState->m_pmapSocketHandle->IsEmpty())
33 {
34 ASSERT(pState->m_pmapDeadSockets->IsEmpty()); //确保当前线程没有未正确关闭的socket。
35 ASSERT(pState->m_hSocketWindow == NULL); //确保未有释放的窗口对象。
36
37 //创建用于接收socket相关消息的隐藏窗口。
38 CSocketWnd* pWnd = new CSocketWnd;
39 pWnd->m_hWnd = NULL;
40 if (!pWnd->CreateEx(0, AfxRegisterWndClass(0),
41 _T("Socket Notification Sink"),
42 WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL))
43 {
44 TRACE(traceSocket, 0, "Warning: unable to create socket notify window!\n");
45 delete pWnd;
46 AfxThrowResourceException();
47 }
48
49 ASSERT(pWnd->m_hWnd != NULL);
50 ASSERT(CWnd::FromHandlePermanent(pWnd->m_hWnd) == pWnd);
51 pState->m_hSocketWindow = pWnd->m_hWnd;
52 }
53
54 //保存当前子类socket和m_hSocket的关联,pSocket传入的是当前线程的this指针。hSocket就是m_hSocket,pSocket就是子类的socket指针。
55 pState->m_pmapSocketHandle->SetAt((void*)hSocket, pSocket);
56 }
57 else
58 {
59 //当 KillSocket 的时候会走到这个分支。
60 void* pvCount;
61 INT_PTR nCount;
62 if (pState->m_pmapDeadSockets->Lookup((void*)hSocket, pvCount))
63 {
64 nCount = (INT_PTR)pvCount;
65 nCount++;
66 }
67 else
68 nCount = 1;
69 pState->m_pmapDeadSockets->SetAt((void*)hSocket, (void*)nCount);
70 }
71 }
72 CATCH_ALL (e)
73 {
74 AfxEnableMemoryTracking(bEnable);
75 THROW_LAST();
76 }
77 END_CATCH_ALL
78
79 AfxEnableMemoryTracking(bEnable);
80 }

总的来说,创建Winsocket的时候大致会做一下三个关键步骤:

1.将这个socket和当前执行线程关联,标准socket作为key,子类WinSocket作为Value存在m_pmapSocketHandle中。

2.创建一个隐藏窗口用于接收消息。

3.调用 WSAAsyncSelect 向系统注册,这个函数的作用就是当相关socket的事件发生时将的消息发给创建的隐藏窗口。CSocketWnd 类中定义了相关消息的处理函数,在消息响应函数中最终调用CAsyncSocket::DoCallBack方法,在这个方法中对不同的socket事件,如发生,接收,断开等通过虚函数形式调用子类的相关方法。也就是你自己实现的相关方法,如OnReceive, OnClose等。

从上面的过程可以看出为什么在不同线程中关闭winsocket就会出现异常了,以及为什么需要先要Detach再Attach一下。(重新绑定线程和Socket的关系。这个重新绑定不仅仅是pmapSocketHandle的key,value修改一下,而且还要重新创建CSocketWnd窗口,因为在Detach的时候这个窗口已经被销毁了。)

仔细想一下,微软为什么要这么做?为什么多线程使用起来这么麻烦一下?其实这么做已是最好的方法了。微软封装了标准的socket以供我们使用,为了大多数时候使用的方便就结合和windows的消息机制。windows消息机制就是当事件发生时系统向指定的窗口发送一个消息,所以在创建winsocket的时候先创建了一个CSocketWnd窗口以接收相关消息。那么当窗口接收到消息后如何再通知给相关的socket呢?在winsocket类中如何得到socket对象从而调用其相关方法呢?我们看到在CSocketWnd消息处理函数中实际上调用的是CSocket静态方法,其实在CSocket中有很多静态方法,在这些静态方法中如何得到具体的socet对象呢?就是通过

1 _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;

这句代码,这句代码在这些静态函数中都有使用。在Socket的构造函数中将使用标准socket生成的m_hSocke对象t和子类的WinSocket对象指针通过AttachHandle静态方法放在pState的m_pmapSocketHandle成员变量中。创建CSocketWnd窗口的线程,创建Socket的线程和接收Socket消息的线程其实都是同一个线程,如图所示:

所以CSocketWnd接收到消息后_afxSockThreadState得到线程对象通过m_pmapSocketHandle就能找到子类的CSocket对象,从而调用相关的虚函数,实现发送,接收,断开等功能。

这里总结下使用Winsocket需要注意的一些地方:

1.创建Socket时要在最好在主线程中创建,因为创建Socket时创建了一个隐藏的窗口,如果创建Socket的这个线程很快就结束了,那Socket的隐藏窗口也就自动被释放了,无法再接收到相关消息了。这种情况下就需要调用Detach和Attach方法重新绑定一下线程,Attach方法会再创建一个隐藏窗口和当前线程关联。

2.关闭Soket时最好用WinSocket的Close()方法,而不是使用socket的closesocket()方法。因为Close()方法会销毁创建的隐藏窗口,取消WinSocket和当前线程的关联。如果只使用closesocket方法,就会引起内存泄漏和如果当前线程再创建WinSocket就可能会出错(因为上次绑定的Socket还未去除)。

CSocket,CAsyncSocket多线程退出时的一些注意事项(解决关闭WinSoket崩溃的问题)的更多相关文章

  1. CoreData和SQLite多线程访问时的线程安全

    关于CoreData和SQLite多线程访问时的线程安全问题 数据库读取操作一般都是多线程访问的.在对数据进行读取时,我们要保证其当前状态不能被修改,即读取时加锁,否则就会出现数据错误混乱.IOS中常 ...

  2. Android退出时关闭所有Activity的方法

    Android退出时,有的Activity可能没有被关闭.为了在Android退出时关闭所有的Activity,设计了以下的类: //关闭Activity的类 public class CloseAc ...

  3. Qt 程序退出时断言错误——_BLOCK_TYPE_IS_VALID(pHead->nBlockUse),由setAttribute(Qt::WA_DeleteOnClose)引起

    最近在学习QT,自己仿写了一个简单的QT绘图程序,但是在退出时总是报错,断言错误: 报错主要问题在_BLOCK_TYPE_IS_VALID(pHead->nBlockUse),是在关闭窗口时报的 ...

  4. Android设置Activity启动和退出时的动画

    业务开发时遇到的一个小特技,要求实现Activity启动时自下向上弹出,退出时自上向下退出. 此处不关注启动和退出时其他Activity的动画效果,实现方法有两种: 1.代码方式,通过Activity ...

  5. HP平台由于变量声明冲突导致程序退出时的core

    最近遇到一个莫名的问题,在HP-UX B.11.31 U ia64平台下,程序PetriService在接收到产品化退出或Ctrl-C时,程序在main函数返回后析构全局的CTQueue<SMs ...

  6. Android 编程下 Activity 的创建和应用退出时的销毁

    为了确保对应用中 Activity 的创建和销毁状态进行控制,所以就需要一个全局的变量来记录和销毁这些 Activity.这里的大概思路是写一个类继承 Application,并使获取该 Applic ...

  7. MFC关于多线程中传递窗口类指针时ASSERT_VALID出错的另类解决 转

    MFC关于多线程中传递窗口类指针时ASSERT_VALID出错的另类解决   在多线程设计中,许多人为了省事,会将对话框类或其它类的指针传给工作线程,而在工作线程中调用该类的成员函数或成员变量等等. ...

  8. 解决log4cxx退出时的异常

    解决log4cxx退出时的异常(金庆的专栏)如果使用log4cxx的FileWatchdog线程来监视日志配置文件进行动态配置,就可能碰到程序退出时产生的异常.程序退出时清理工作耗时很长时,该异常很容 ...

  9. fork多线程进程时的坑(转)

    add : 在fork多线程的进程时,创建的子进程只包含一个线程,该线程是调用fork函数的那个线程的副本.在man fork中,有The child process is created with ...

随机推荐

  1. 手摸手,带你用Beego撸商城系列二(登录篇)

    完整项目地址: go-shop-b2c 系列文章: 手摸手,带你用 Beego撸商城 系列一(基础篇) 手摸手,带你用 Beego撸商城 系列二(登录篇) 手摸手,带你用 Beego撸商城 系列三(系 ...

  2. hdu 2604 递推 矩阵快速幂

    HDU 2604 Queuing (递推+矩阵快速幂) 这位作者讲的不错,可以看看他的 #include <cstdio> #include <iostream> #inclu ...

  3. 如何在微信小程序的模板渲染中使用JS?

    在微信小程序中使用模板渲染时,可能需要用JS对其进行处理. <view class="price text-red text-lg"> <!-- 价格保留两位小数 ...

  4. Rsync+Sersync数据实时同步(双向)

    Rsync+Sersync数据实时同步(双向) 服务介绍 一.为什么要用rsync+sersync架构? 1.sersync是基于inotify开发的,类似于inotify-tools的工具 2.se ...

  5. 初探 Redis 客户端 Lettuce:真香!

    一.Lettuce 是啥? 一次技术讨论会上,大家说起 Redis 的 Java 客户端哪家强,我第一时间毫不犹豫地喊出 "Jedis, YES!" "Jedis 可是官 ...

  6. Python分析【公众号】历史评论,看看大家的留言情况!

    大家好,我是辰哥~~~ 辰哥玩公众号有一段时间了,这期文章分析一波读者的留言情况,不仅可以对公众号的各位铁粉一目了然,还可以通过分析的结果对公众号的经营进行更好的规划.如读者留言的内容通常是内容是什么 ...

  7. SpringBoot集成Quartz实现定时器

    SpringBoot+Quartz实现定时器,由于本人也是刚学习,不足之处请各位大神指正 .. 1.pom配置   <dependency>   <groupId>org.sp ...

  8. hdu 2092 整数解(一元二次方程解)

    题目: 思路: 1.两个整数的和和积容易联想到一元二次方程的两个根,只要证明有两个解,并都是整数就打印出Yes,否则打印出No 2.最后判断那步,为什么只需要判断一个整数存在就够了,因为和是整数,一个 ...

  9. EF Core3.1 CodeFirst动态自动添加表和字段的描述信息

    前言 我又来啦.. 本篇主要记录如何针对CodeFirst做自动添加描述的扩展 为什么要用这个呢.. 因为EF Core3.1 CodeFirst 对于自动添加描述这块 只有少部分的数据库支持.. 然 ...

  10. Django基础-04篇 Django开发前后端联动

    1. 写views views.py代码块 1.在前端以/article/{{ article.id }}这种方式请求后台, 参数配置在urls.py中path('category/<int:i ...