NetHost指针访问安全专集(基于mdk1.10)

百般思念 提交于 2019-12-07 12:01:06

NetHost是服务器引擎的2大接口类之一,而且是由引擎维护其对象的创建与释放,

用户使用不当可能造成访问野指针或者内存泄露。

为了让使用mdk的朋友,安全的使用NetHost指针,本文写提出一些安全的使用模型。

首先说明一下,如果担心NetHost使用不当,可以完全放弃NetHost,引擎的另外一大接口类NetServer已经提供了SendMsg()与CloseConnect()方法,用于安全的操作主机的连接,只要传递HostID进去(可以通过NetHost->ID()方法得到)即可,ID只是一个int值,不存在内存泄露与野指针问题。

接收数据,在OnMsg中使用参数的NetHost指针是绝对安全的,引擎已经做好控制。


开始NetHost的安全访问

NetHost表示一个连接上来的主机,该类主要就是2个作用send发消息close断开连接,另外提供方便的Group操作


为了避免方法野指针和内存泄露,主要要了解NetHost对象的生存周期
NetHost对象在连接建立时被创建
连接断开(不管是server断开,还是client断开)后被移动到关闭列表(代码中就是m_closedConnects里面),
后台有一个释放线程,会循环定时去检查这个列表中的指针,如果对象的访问计数为0,则释放掉对象

对象什么时是绝对安全的(不会被释放)
1.被作为参数传递给OnConnect OnMsg OnClose时,参数中的NetHost指针是绝对不会被引擎释放的,
因为引擎在调用这3个方法前已经将对象的方法计数+1,在退出后会将访问计数-1,也就是说这3个方法一退出,对象就有可能被释放。
2.OnClose被调用前,NetHost对象绝对不会被引擎释放,也就是说OnConnect OnMsg退出后,指针依旧是安全的,
但这不表示,你在OnConnect中将指针复制以后,下次在OnMsg中使用就是安全的,因为OnConnect OnMsg OnClose是并发的

为了不让底层释放对象,用户可以使用唯一的方法——使用Hold()来将访问计数+1,
用完以后记得使用Free()来将访问计数-1,好让引擎最终有机会释放对象,否则引用计数无法归0,引擎永远不会释放对象,这就是内存泄露

下面举例说明一些需要Hold Free的情况
作过服务器开发的应该很容易想到,一个连接建立了在OnConnect中就要保存表示这个连接的对象,
将来在OnMsg或其它业务中,用这个对象来通知其该玩家,没错服务器对于连接的使用就是这样,没有其它花样。

那么我们建立一个map<int,NetHost*> list用于保存所有连接
比如有3个玩家abc连接进来
onconnect(pPlayer)
步骤1.lock list
步骤2.pPlayer->Hold()访问计数+1
步骤3.将指针保存到list  //abc到了list里面,并且访问计数=1,即使连接断开,在用户调用Free()之前,引擎不会释放他们
步骤4.unlock

onclose(pPlayer)
步骤1.lock list
步骤2.将指针从list删除
步骤3.unlock
步骤4.pPlayer->Free()不在使用了,访问计数-1

比如玩家a攻击了玩家b
onmsg(pPlayer)
步骤1.lock list
步骤2.在list中找到玩家b的NetHost指针赋值给pPlayerB
步骤3.unlock
步骤4.pPlayerB->Send()通知玩家B,受到玩家A的攻击

现在开始做情况分析
if步骤4之前,玩家B退出游戏,并且发生线程切换,进入onclose()
结果就是pPlayerB指向的对象访问计数归0,引擎获得了该对象的释放权利(虽然不一定会立刻释放,因为是定时去检查要不要释放)。
但这就已经不安全了,服务器7*24小时长期运行,而且几千用户连接断开,要碰到正好引擎把指针给释放了,几率非常高。
结果就是线程切换回onmsg执行步骤4时,访问一个野指针,服务器崩溃。

就是说在步骤4.之前要pPlayer->Hold()1下,让访问计数变为2,
万一这时B退出,访问计数还是1,引擎不会释放对象,
步骤4之后,再pPlayer->Free()下,如果B退出了就是0,没退出就1,正常

这样就没问题了?
不对pPlayer->Hold()和pPlayerB->Send()没有区别都是在unlock之外使用pPlayerB这个指针,同样会被线程切换打断,导致访问野指针
所以pPlayer->Hold()必须放在lock unlock内。这样就没问题了

总结一下就是,指针进入列表,从列表取出来使用,都要在lock unlock之内完成hold,因为hold在lock之内,可以阻止释放线程先行free,因为free要在从列表删除之后,也就是lock之后(线程会被挂起),而free可以在unlock前(互斥区内),也可以在unlock之后(互斥区外)进行,是安全的。

这样的话,如果OnMsg步骤1lock之前,玩家B退出游戏,发生线程切换,OnClose中完成free,则线程再次切换回OnMsg时候list中已经取不到玩家B的对象了,表示玩家已退出,一切正常。


最后OnMsg中不Hold Free可不可以?
答:可以,将步骤4放到lock之内就行了

这样最简单,
onconnect()时lock hold,进列表
onclose()时lock从列表删除unlock free
要用的时候lock,要么对象已经被从列表中删除,要么阻止onclose线程,用完unlock,不必hold free

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!