.

网络游戏架构的前世今生网络连接

白癜风诚信企业 http://disease.39.net/yldt/bjzkbdfyy/6169057.html

接上文

2.2

网络连接方案

?

相比于网络同步方案,游戏在网络连接的方案上和其他应用上并没有太大差异。有些轻量级休闲游戏,会选择HTTP/HTTPS等短连接方案,少量利用websocket全双工做一些主动消息推送业务。这种方案的好处在于,有大量成熟的三方库和参考案例,并且不局限于游戏领域之内,门槛低、方便实现并易于更新迭代。

虽然HTTP可以用于大流量的通信场景,但对低延迟通信来说并不是 的选择,相比之下,主流的游戏网络连接方案还是TCP长连接,这是因为短连接会不断的创建和释放连接,既消耗服务器性能又增加了平均延迟。

即使当今的HTTP库通常都自带连接池,短连接的建立和释放并不是一一对应TCP的握手和挥手,但应用层不必要的建立和释放过程也会影响程序运行性能,在玩家数量高时尤为明显。

MUD、MUX等游戏会选择Telnet等普遍使用的TCP长连接协议,是由于这些游戏往往在客户端渲染表现上投入较少,在游戏世界的设计和玩法内容设计上投入较多,所以会使用较为常规的长连接协议减轻客户端工作量。另一个方面原因,这些游戏往往可以通过统一的客户端进行登录游玩,保持协议的统一也是游戏间的互相促进和成就。

客户端界面更用户友好的游戏,还是会以自定义TCP长连接协议居多。对于网络安全要求较高的公司和游戏产品,还需要在协议上进行加密。我所服务过的好几家大型游戏公司,内部都有专门的团队在做网络协议、网络加密的更新迭代,这也是游戏背后的网络攻防战(网络安全不属于本栏目的话题,在这里仅作基本介绍)。游戏中的自定义网络协议并不像某些更专业的领域那么严格(如IOT等,学习看懂都是一件很复杂的事),通常需要考虑以下几个点:

粘包拆包问题的处理

包体的序列化与反序列化

包头包尾是否需要特定标识

//伪代码展示网络连接的处理逻辑,语法使用golangfor{...//预处理判断网络连接是否正常,设置读取deadline等bytesLength,err:=tcpConnection.Read(b)//从网络连接中读取数据iferr!=nil{//错误异常处理}ifbytesLength0{ringbuffer.PushPacket(b[:n])//将当前收到的数据包塞入ringbuffer//一次获取到的网络数据包中,可能是包含数个网络包粘包的结果,循环处理for{//通过codec解码buffer中的包体outBytes,err:=codec.Decode(ringbuffer)iferr!=nil

outBytes==nil{//暂未收到完整包,或者有错误异常}...//自定义一些特殊包的处理,例如心跳包等agent.OnMessage(outBytes)//向业务层agent通知收到消息事件}}}

一般来说,上述三个点中只有前两个点是相对必要的。粘包拆包问题并不是游戏行业的特有问题,很多成熟的网络库提供好了现成的方案,如果自己编写的话也不难,只需要选择适合的codec即可。例如在包头中带包体长度,或是发送定长包体等。

//以带包体长度(LengthFieldBased)为例,b为序列化后的输出结果func(codec*Codec)Encode(b[]byte)(out[]byte,errerror){length:=len(b)//获取长度out=getLengthBytesByBigEndian(out,length)//获取大典序的长度bytesout=append(out,b...)//拼接加码结果return}func(codec*Codec)Decode(buffer*RingBuffer)(out[]byte,errerror){//buffer读指针不移位的情况下读取4位,获取包体长度lengthBuffer,err:=buffer.LazyReadN(4)iferr!=nil{return//没有4位可以读,包不完整}else{frameLength=getFrameLength(lengthBuffer)//获取包体长度的数字}//buffer读指针不移位的情况下读取{包体实际长度位}4位,获取实际包体内容body,err:=buffer.LazyReadN(frameLength)iferr!=nil{return}buffer.ShiftN(frameLength+4)//buffer读指针移动到正确位置//注:这里千万要注意LazyReadN的返回值是值拷贝还是引用拷贝,需要复制出一份新的内存数据出来out=make([]byte,frameLength)copy(out,body)return}

序列化与反序列化的方式也有很多,不过从传输的效率和性能上考虑,选择protobuf或其他高压缩率的序列化方案是主流,这一点和其他应用不太一样。其他应用可能会选择json、yaml等主流通用的序列化方案,这些方案的三方库很多,解决方案也多。但这些往往需要占用更多的网络带宽。我在之前的项目中,一般都是以公司层级去写自己公司的网络序列化库(即公司旗下所有游戏都是公用同一个网络库),这样既安全又高效;不过这两年protobuf用的更多,生态和开发效率上都是更好的选择,尤其在给新入职的程序员做介绍时,自己写的库往往要讲半天,protobuf更好上手,资料肯定比公司自己写文档要全。

当然,开发者们对游戏性能的追求是无止境的,这其中也包含网络连接。TCP作为最主流的传输层协议,在高峰用网期间是会受到一定影响的,近几年来尤其如此;并且由于其设计上的限制,导致在跨国跨洋的场景上往往不尽如人意。这一点无论是对短连接HTTP(S),还是长连接自定义协议都是如此。为了优化玩家的游戏体验,我们自然把目光放到了另一个耳熟能详的传输层协议——UDP上,希望UDP能够优化游戏的网络连接。

最最开始,UDP只是在有限的游戏流程内进行优化,但很快,部分游戏把UDP作为“最终杀器”完全取代了TCP。老一辈即时战略类游戏如魔兽争霸3,使用UDP进行信息的广播;部分有区服概念的RPG,使用UDP进行网速检测、玩家区服信息的传输。MOBA等对网络延迟要求很高的游戏,或是在东南亚等网络条件复杂地区发行的游戏,会使用UDP去模拟TCP做有状态连接,在第七层应用层做自定义UDP协议,从而达到有连接并保序的要求。在我经历过的项目中,使用过Raknet、ZeroMQ(UDP)、KCP库进行过自定义UDP协议的开发。我个人的经验而言,KCP是我用起来最顺手,也是测试下来最稳定的开源库,在印度到美国的跨洋连接上也有不俗稳定的表现。

可以参考我之前练手的一个开源项目


转载请注明:http://www.abachildren.com/hbyx/580.html