初入游戏整理

游戏开发与我之前的互联网开发最大不同就是协议数据发送接口调用。在网站开发和维护中,一般很少直接调用到通信协议发送的相关接口。我们可以利用Spring Restful接口直接进行业务的开发。

因此在开发中,容易疑惑一个问题。TCP/IP到底可靠么?这是听了老大讲的充值例子所疑惑的问题。

充值案例讨论

手游开发,我们经常会遇到一个问题。那就是网络波动巨大。例如,玩家在进电梯前点了一个游戏币下单按钮(假设没有订单号,客户端没做处理)。这个订单会首先发送到网关,分配到对应的服务器进行处理。处理完订单后,将数据更新给客户端。但是玩家此时进入电梯了。网络中断,他没更新到信息。于是老哥开始狂点。接下来会发生什么事呢?是的,玩家一次性订购了多批游戏币。这是非常严重的游戏资产问题。

这里很简单了解到TCP/IP是可靠的。它做到三个很重要的事情

  1. 确保了丢失的数据重发。尽管你在电梯里网络断绝,但是只要你出电梯,恢复了网络,那么TCP就会重新发送消息。尽管这导致了玩家一次性下了多个订单。
  2. 消除重复。这里的重复是IP消息的重复,而不是订单的重复。
  3. 数据包被重新组装成它们被发送的顺序。

但是上面问题还有个版本,假设进电梯前,客户端已经发送出去了消息。服务器已经接收到了信息,订单已经下了,也回复了客户端。但玩家这个时候进入了电梯,网络已经中断了。客户端无法确认自己的消息是否被服务端接受了。客户端在电梯期间数次重发不成功后,它放弃这条协议。但是经理要求客户端会自动重发充值信息(业绩啊)。于是客户端再次发送了消息。这里,玩家在不知情的情况下,他被重复下单了。

第二个版本TCP似乎又是不可靠的了。它不会确保你的消息必达。同时,客户端不知道自己的消息到底被服务端接受了没。如果接受了,那么它就安心等待服务端更新数据就好了。

TCP到底可靠么

首先,通信协议不可能是绝对可靠的。这里涉及一个经典问题,拜占庭将军问题。以下摘自百度百科。

拜占庭将军问题(Byzantine failures),是由莱斯利·兰伯特提出的点对点通信中的基本问题。含义是在存在消息丢失的不可靠信道上试图通过消息传递的方式达到一致性是不可能的。因此对一致性的研究一般假设信道是可靠的,或不存在本问题。

这里的一致性就是指A端发送消息M到B端后,B端收到后,发送回确认M1的情况下,端A知道端B已经收到了消息,端B知道端A已经收到了确认M1。基于信道的不可信,我们知道这永远不可能实现。因为每一个确认也需要端去回复。如上面例子中,M1是针对M的确认。为告诉B端A端收到了确认,A端需要发送给B端消息M2。M2是针对M1的确认。假设在一系列消息M,M1,M2 ... Mn后,系统达到了一致。其中后一个消息是前一个消息的确认。但因为信道的不稳定,我们永远无法确认最后一个消息的必达。因此,永远无法确认最后第二个消息是否已达。以此类推,无法证明消息M已达。违反了假设。

上述反证证明了,在信道不稳定的情况下,无法重复(每次派出信使只有一个且他也不回来了)的双端通信无法使双发达到一致性。对此,TCP/IP协议第一个解决方案——超时重传。信道不稳定也就意味着消息必达是有一定概率的,那么TCP/IP协议最多只要重发消息足够的次数就可以确信自己可以收到一个来自接受方确认。到这里,单端的通信就完成。发送方发送了消息,并确定了接收方一定接受了消息,无论接收方到底是接受了1个还是复数个相同的消息。接收方会自己完成去重。同时,发送方为了避免陷入将军问题中的无穷确认,不会对接收方发来的确认进行确认。发送方不在乎接收方是否知道发送方已经收到了确认。

上面的过程中,发送方对接收方的单端通信已经完成。如果接收方需要知道发送方是否收到了确认,它只需要按照同样的过程发送一个询问是否收到了确认的消息就可以了。至此,我们在信道不稳定的情况下,通过分解为两个单端通信和多次超时重传完成两端消息的互通。实际中,这可能会更加复杂。例如,这里的重发不会是无限次,达到一定次数,TCP协议会停止重发,并抛弃相关报文。

TCP就是基于以上的机制成立的。这也是为什么TCP三次握手的原因。端A发送给端B syn报文,端B回应 ack报文。端A如果收到了ack报文说明A到B的单端通信构建成功。端B发送给端A syn报文,端A回应 ack报文。端B如果收到了ack报文说明B到A的单端通信构建成功。只不过在通信刚开始阶段,双发没有任何需要发送的数据,因此可以合并端B发给端A的ack报文和syn报文。四次挥手是因为双方无法确认对面数据是否发送完毕,无法合并中间的两个报文。

至此,我们知道TCP是不可靠的。

结语

以上,我们可以得到两个开发教训

  1. 应用级别的确认是必要的。网络硬件问题永远是无法预测的。仅仅依靠TCP协议,必定有部分报文发送失败。如果没对这些报文进行应用级别的确认和重发,这些报文相关的消息可能会永远丢失。

  2. 网络接口需要确保幂等。即便多次发送协议,也要确保它不会对系统的一致性造成伤害。例如,上面的充值订单可以加一个订单ID,防止一个订单多次确认。客户端在订单确认期间加以限制。服务端不允许短时间内,玩家多次下单,例如1秒内10单绝对有问题。

posted @ 2020-03-14 18:21  SourMango  阅读(145)  评论(0编辑  收藏  举报