本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/evilswords/archive/2010/09/01/5855572.aspx
为何要组包
首先一个原因避免读写频率瓶颈:不管百兆还是千兆网卡,消息的收发对通讯io的调用都会产生中断,而这个中断限制了每秒不能无限制的写入/读出,目前网卡抛开系统和cpu瓶颈驱动级的测试瓶颈是60w/s,而笔者所用ace通讯框架在windows平台下按前摄器(proactor)模式封装的测试,读写瓶颈在接近10w/s。假设针对某个socket每秒有300条写入消息(这个数字在广播范围内密集战斗中还属中等要求),那么抛开消息大小来说,这种频率下能够支持的同时读写的socket数也就300个!但如果能让每80条消息组成1条,理论上支持的同时读写的socket数量也能上升80倍,接近2w了
另外一个原因节约冗余的消息头:tcp 和ip头会占用近20字节,加上你自定义的消息头,加密,压缩key值这种头应该也会接近20字节了,如果信息的同步只占用8字节,这冗余的头就相当于实际使用信息的5倍,还是拿每秒300条,每80条消息为组包单位,则不组包要产生约40Bx300x8 ≈ 93kb的冗余信息,2000个链接下就有180Mb的冗余信息!!
Ok,不压包的情况下只有2种选择:1避免上规模的范围广播;2限制玩家链接数量…看上去对于游戏服务器来说都不像是个好办法
如何组包
理论上组包很简单,就是把小个的信息压到一个大个的消息块里,为了拆包方便当然不可少还得把信息的类型也同时压入,直到消息块达到或接近消息块长度限制然后发出去,还有个问题,如果压入消息少迟迟没有达到发送的长度,消息岂不一直等待,所以还需要个定时器按规定帧率检查是否有压入数据需要发出。
拆包要复杂点,因为压入的信息不同,所以当你搜到一个大消息的时候并不知道里面的信息如何分布,有点类似底层通讯处理流信息时候的情况,得自己拆包(好在没有处理粘包的情况),推荐的做法是给每种消息都对应一个消息类来处理接收的,在buff第一个字节判断完类型后,偏移到数据段,拿对应的消息类指针去指向这段,如果实现的消息类有继承体系存在虚表的偏移,则需要在类里面存在一个数据指针(消息类如果处理发送逻辑的话这个数据指针也是有用的),用这个指针去指数据段,消息类里顺带一个处理的方法一并解决逻辑了。处理完一个消息了以后对buff进行刚刚所指数据结构大小的偏移,进行下一个消息的处理
什么层次的组包
选择在通讯底层组包还是逻辑上层组包。
在通讯层组包的优点很明显,上层根本不用关心消息是以什么形式发送出去的,就像应用层不去管Tcp底层协议到底怎么个保证消息正确到达目的地。而这个组包的操作也很容易划分到现有框架里agent的功能中。理由很简单,所有服务器发给客户端的消息都通过这,在这相对容易在检查时间内组上包,而且在这有消息的加解密处理,正好可以再把组拆包处理也放进去。这个方案也还是存在一些问题,首先agent要求消息的快速转发,为了满足每个socket的并行处理,已经管理了不少线程,按上面描述的逻辑在原本的发送线程里组包还要额外一个定时线程去检查是否要发出待完成组包数据,这样每个socket就都有收包组包发包3个线程形成了环形互斥,经过测试这样会让收发效率受到不小影响,其次针对性不强,在agent上组包是以socket为单位,把各个服务器规定时段内发向该socket的消息组包,而组包消息之间的联系无法保证。假设情况1 有个消息需要及时发出,但是被agent拦下组包;假设情况2 有个消息后续还有个关联的消息,本来想组一块的,结果被另一个服务器的消息插入到其间;假设情况3 有几个消息有共同性,除了组包还可以去除一些冗余信息;这些情况用agent来组包的方式并不容易实现。
描述了不少底层agent组包的局限性也不难看出笔者倾向于选择后一种做法:在逻辑层上组包。先说它的缺点吧:逻辑层要关心消息发送的手段区分处理及时发送和非及时发送的组包消息,这样会让逻辑层处理看上去不够单纯。其实作为游戏服务器面临的通讯压力绝大多数来自视野信息的同步消息,这种消息包括属性改变,位置同步,战斗操作,物体创建等,而另外的功能交互型消息需要及时反馈的居多不太适合组包,查询型消息的反馈一般信息已经足够大了组不组包都一样,聊天型消息一般有自己的压缩逻辑甚至不走游戏内的通讯连接也不适合组包。回头来看视野信息同步消息,这种消息集中在场景逻辑里,特点是每秒产生的数量多,单个信息小,关联性大,需要范围广播,一切特点看来都非常适合组包。而一般场景逻辑每个玩家对象自身携带逻辑帧驱动,检查等待发出的组包消息处理可以很方便的放在里面驱动,对原本的服务器设计没有任何影响。同时消息的缓冲也可以放在玩家对象上,利用一样的内存管理策略不用担心临时变量的消耗。最后场景逻辑一般可以分为多个zone服务器在处理,所以不用担心组包操作对整体性能会产生多大的影响。发送实现起来也想当容易,只要在玩家类原有的发送接口上加个sendcachemsg,调用sendmsg的时候想想是不是需要及时发送就行了。
有人可能会有想法,组包能不能不放agent的通讯底层而放zone的通讯底层呢,这样不用担心agent转发效率也不用劳烦写逻辑的人费神。呃~好像不行,因为zone通讯底层只有针对服务器内部的连接,而没有针对某个客户端socket的连接,从而也没有组包的基础单位了。这里补充说一下服务器间的通讯消息没必要组包因为一般要么通过127本地走要么在一个机房走,而客户端的请求消息组不组包就看游戏的操作需求要不要求反应灵敏了。
组多大的包合适
相信看到这应该有“当然越大越好”这种想法,但实际情况并不如此。越大的包在复杂网络环境下会意味着重发的概率越高,使得在内网测试的一些理想数据放到外网会有很大的差距。对于TCP协议来说,整个包的最大长度是由最大传输大小(MSS,Maxitum Segment Size)决定,MSS就是TCP数据包每次能够传输的最大数据分段。为了达到最佳的传输效能TCP协议在建立连接的时候通常要协商双方的MSS值,这个值TCP协议在实现的时候往往用MTU值代替(需要减去IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes)所以往往MSS为1460。经过项目的测试,1024的包往往是效率最高的(2的阶乘数是很神秘的,你懂的)
再补充几句
windows平台和linux平台,选择用tcp协议通信,默认情况下会调用Nagle算法,这个算法的核心和上面描述的东西差不都,为的就是减少小包包头对带宽的占用,但由于是针对链接的算法,本身适合的环境有限,如果短时间有很多小包往一个链接发送,这个算法无疑能起到很好的效果。但发送给客户端的消息往往不是一个链接能搞定的,而且上层做了组包后包大小超过256,Nagle算法几乎起不到作用反而空开了缓存白转了些判断逻辑。所以如果选择上层组包的方式需要显示调用关闭Nagle,在设置sock的时候改成TCP_NODELAY参数