来到公司后了解到我们的游戏服务器是使用 tcp 协议进行通信的,使用 epoll 来监听和处理 socket. 网络协议会一定程度游戏数据的网络传输的效率、安全性、完整性、顺序性等,因此网络协议的选择一定要恰当.
# 1.TCP 与 UDP
# (i). 区别
大概回忆下 TCP 与 UDP 的主要区别:
- tcp 面向连接,提供可靠服务;udp 无连接,尽最大可能交付.tcp 对于发送失败的包的会进行重传,所以 tcp 的服务相比 udp 可靠性更强.
- tcp 实现较为复杂,虽然使用方便,但是在使用时想修改起来非常麻烦,无法做到自定义地制定一些规则.udp 本身十分简单,没有握手、重传等的机制,只是简单地将数据报发送至网路上,但这样也使得 udp 的可修改性很强,基于 udp 可以实现项目需要的可靠性、流量控制等功能。感觉 tcp 和 udp 的这点区别类似于 java 和 c++, 使用 java 时不需要关心内存管理,但你也无法像 c++ 那样去控制它的内存管理.
- tcp 为了保证可靠性,引入滑动窗口、握手等的机制,所以会影响传输数据的效率,相比 udp 效率会偏低.
# (ii). 选择
其实这些区别在应用在游戏服务器上时,区别主要就体现在延迟上了.
在网络情况良好的情况下,两种协议其实没太大区别,但当网络情况拥堵,开始发生丢包时,使用 tcp 则会重传,保证包都发送成功,但同时会引起延迟。使用 udp 可以有效改善丢包引起的延迟,但丢失的包是不会重传的,但可以通过编程的方式实现重传.
所以一般来说,tcp 和 udp 的选择主要考虑的就是对延迟的可容忍程度.
- 当对游戏的延迟不太可容忍时,如一些实时交互的游戏,MOBA 和 FPS 游戏,那么 udp 比较合适
- 当对游戏的延迟可以容忍时,如一些卡牌游戏,那么使用 tcp 就比较方便
但也只能说是比较合适,并不是绝对的。比如魔兽世界 wow 就是使用 tcp 作为传输协议的。说实话知道这点的时候还是比较意外的。不过经过查阅资料,发现 wow 会有隐藏这种延迟的办法.
玩家的攻击指令发送给服务器的操作是放在比如 “attack_entity(entity_id)” 或者”cast_spell(entity_id, spell_id)“的接口中来做的,换句话说,瞄准操作是独立于进行的。如此一来,一些类似发起攻击动作和释放技能特效就能够在没有收到服务器确认的情况下就直接执行,比如展现冰冻技能的效果就可以在服务器没有返回数据前在客户端就做出来。
我们的游戏也是 mmorpg, 也是具有实时性的,也会有类似的处理。比如我们的商店列表,是在登录时发送至前端,然后在前端缓存,当发生变化时再由服务器发送通知客户端更新,购买时再在后端进行验证,通过才会进行购买动作.
总的来说,就是尽可能把关于展示的计算放在客户端,服务器负责变化时的同步与动作的验证,这样可以规避一些延迟带来的影响.
# 2. 可靠 UDP
基于 udp 我们可以根据需要来实现可靠性、流量控制等功能,很多时候可以有比 tcp 很低的延迟、更高的可控性.
现在已经有一些开源的可靠传输协议:QUIC、ENET、KCP、UDT. 其中 KCP 是国内开发的,而且表现很不错,所以我去研究了一哈~主要参考 KCP 的官网文档
其实 kcp 主要做的是在底层协议 (一般是 udp) 的基础上,加上一层对丢失的数据包进行重发,来保证可靠性。与 tcp 相比,kcp 对一些处理进行了一些改良,从而在保证可靠性的同时加快了传输效率。官方文档中有提到 kcp 的一些特殊处理:
TCP 是为流量设计的(每秒内可以传输多少 KB 的数据),讲究的是充分利用带宽。而 KCP 是为流速设计的(单个数据包从一端发送到一端需要多少时间),以 10%-20% 带宽浪费的代价换取了比 TCP 快 30%-40% 的传输速度。TCP 信道是一条流速很慢,但每秒流量很大的大运河,而 KCP 是水流湍急的小激流。KCP 有正常模式和快速模式两种,通过以下策略达到提高流速的结果:<br></br>
# RTO 翻倍 vs 不翻倍:
TCP 超时计算是 RTOx2,这样连续丢三次包就变成 RTOx8 了,十分恐怖,而 KCP 启动快速模式后不 x2,只是 x1.5(实验证明 1.5 这个值相对比较好),提高了传输速度。<br></br>
# 选择性重传 vs 全部重传:
TCP 丢包时会全部重传从丢的那个包开始以后的数据,KCP 是选择性重传,只重传真正丢失的数据包。<br></br>
# 快速重传:
发送端发送了 1,2,3,4,5 几个包,然后收到远端的 ACK: 1, 3, 4, 5,当收到 ACK3 时,KCP 知道 2 被跳过 1 次,收到 ACK4 时,知道 2 被跳过了 2 次,此时可以认为 2 号丢失,不用等超时,直接重传 2 号包,大大改善了丢包时的传输速度。<br></br>
# 延迟 ACK vs 非延迟 ACK:
TCP 为了充分利用带宽,延迟发送 ACK(NODELAY 都没用),这样超时计算会算出较大 RTT 时间,延长了丢包时的判断过程。KCP 的 ACK 是否延迟发送可以调节。<br></br>
# UNA vs ACK+UNA:
ARQ 模型响应有两种,UNA(此编号前所有包已收到,如 TCP)和 ACK(该编号包已收到),光用 UNA 将导致全部重传,光用 ACK 则丢失成本太高,以往协议都是二选其一,而 KCP 协议中,除去单独的 ACK 包外,所有包都有 UNA 信息。<br></br>
# 非退让流控:
KCP 正常模式同 TCP 一样使用公平退让法则,即发送窗口大小由:发送缓存大小、接收端剩余接收缓存大小、丢包退让及慢启动这四要素决定。但传送及时性要求很高的小数据时,可选择通过配置跳过后两步,仅用前两项来控制发送频率。以牺牲部分公平性及带宽利用率之代价,换取了开着 BT 都能流畅传输的效果。
# 3. 总结
总的来说,选用何种传输协议,还是要因地制宜,根据游戏实际情况以及一些实验测试来决定.
// 不过我也没有做过实验 (✺ω✺), 主要参考了以下文章~
游戏服务器:到底使用 UDP 还是 TCP
为什么 MOBA、“吃鸡” 游戏不推荐用 tcp 协议 —— 实测数据
kcp 协议详解