先放两个链接
最近在玩王者荣耀,发觉两件事:
1. 可以 4G 和 wifi 无痛切换
2. 当网络不好的时候,发出去的消息并不保证到达服务器。比如你在很卡的时候做了某些操作,等网络恢复之后你会发现,你还是在某个没有做那些操作之前的状态
结合这两周阅读的 KCP,我能想到的是,对于第 1 点基于 KCP 就可以实现。
初读 KCP 的时候,对 conv 变量的存在十分不理解,为什么要用它来表示一个会话,直接由 recvfrom 返回的源 IP 和 port 做 key,找到对应的 ikcpcb 不好么。
有这个想法是因为脑海中始终还想着 TCP 那一套,也就是 用一个四元组来唯一确定一个连接。直到我后来再次想到上面第 1 个问题,才想明白,在 UDP 之上定义连接的概念,如果还是将它跟四元组绑在一起,那无非是实现了另一个 TCP。而当你用一个 conv 变量来标识一个连接时,这个协议的连接也就天然的跟源 IP 和 port 无关,也就是随便你变换 IP 地址和端口,只要你发来的 conv 是一样的,我就认为你是同一个用户。当然,代价就是你得开始关心 conv 的生成与校验,这又是另一个话题。
对于第 2 种情况(暂且不论这种做法是否玩家体验最好),却不像 KCP 的行为,因为 KCP 是能保证发出去的数据包一定到达服务器的。
而云风那篇博客里提到的以请求的方式来保证 UDP 的可靠,貌似恰好可以做到符合王者荣耀的表现。也就是可以做到:1. 允许丢包 2. sn 严格递增
可问题是 服务器发给客户端的数据是不允许丢失的,这可怎么办。。
协议层没给的承诺,只好有业务层来做咯~
首先是帧同步,并且服务器会保存当前这一场的所有帧的数据。那么如果客户端收到了编号为 1,2,5 的数据帧,当收到编号 5 的数据帧时,发现没有收到 3,4 帧,就要向服务端索要丢失的这两帧数据。也就是在客户端的业务层也实现一套请求机制,只不过客户端业务层请求的是数据帧而不是协议层的数据包,服务端对数据帧是没有过期时间的,所以客户端发了请求就一定能求来数据帧!
还有一个问题,就是在游戏大厅时,服务器发给客户端的消息,也要保证送达,这个怎么办?因为大厅里的消息并非所谓帧同步,理论上服务器也不会保存那些临时消息,所以如果还是基于这种请求模式的话貌似就不太靠谱了。我个人感觉这个时候用 KCP 比较好。当然,场内也可以用 KCP,只不过是另外一种效果。
总结一下这两种模式:
1. ack + 超时重传 -> KCP 可靠 快速重传 但是当网络差到快速重传也发不到的时候,服务器会堆积大量需要重传的数据包
2. req -> rudp 会丢包 但是保证包的顺序 不管网络好坏,服务器发送数据都很平稳
更新
修改 KCP 也可以实现王者荣耀的效果。
ikcp_send 前,先检查 ikcp_waitsnd,如果 waitsnd 大于某个阈值比如 64,则认为当前网络太差,然后不调用 ikcp_send,丢弃这个数据(或者上层消息队列继续缓存这个数据,从王者的表现来看是直接丢弃的)。同时启动一个计时器,如果 waitsnd 持续 10s 都大于 64,则认为网络奇差无比,根本无法游戏,销毁 kcpcb,断线处理。如果 10s 内 waitsnd 一旦小于 32,则认为网络恢复正常,取消掉前面的计时器。