Loading [MathJax]/jax/output/CommonHTML/jax.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >网络游戏同步模型

网络游戏同步模型

作者头像
changan
发布于 2020-11-19 01:32:11
发布于 2020-11-19 01:32:11
3.2K00
代码可运行
举报
运行总次数:0
代码可运行

常见的网络游戏同步模型

何为网络同步,通俗点讲,就是在一个网络游戏里有玩家A和B同框,当A释放了一个技能,状态发生了变化,B又是如何及时表现A的当前状态的呢,就是通过网络同步技术。 不同的同步模型,目的都是为了保持每个客户端的状态一致,而一般客户端的初始状态是相同的,不同的同步模型采用不同的方式,其实就是在玩家有操作输入时,让所有玩家的客户端的状态仍能够保持一致。 假设客户端的某一对象的状态初始为S0,而玩家的输入为It,玩家输入后根据逻辑F产生了一个状态的变化SΔ,那么在某一时刻n的状态Sn,理论上是Sn=Sn1+SΔ,考虑到初始状态的话

即为了让所有客户端的Sn一致,不同的模型采用了不同的方案。当前常见的同步方案主要有两种,即锁步同步(LockStep,又被称为“帧同步”)和状态同步(StateSync),两者的本质区别在于

  1. 锁步同步。上报客户端的输入It,服务器(或某台Host主机)再定期将某一时间端内(即锁步),所有玩家的It同步给每一个客户端,由客户端计算得到状态Sn。即以客户端的角度来说,上下行包都只有输入It。
  2. 状态同步。上报客户端的输入It和(或)Sn,服务器根据It计算得到Sn,再将Sn同步给客户端。即以客户端的角度来说,上包是输入It和(或)Sn,下行包是Sn。

确定性,即给定相同的初始条件和相同的输入集,能够得到完全相同的结果。对于锁步同步,因为只同步了,所以必须要保证严格地确定性,才能保证每次得到的状态相同,后续的状态才不会产生偏移。而对于状态同步,所有客户点以服务器的状态为准,所以保证关键状态与服务器下发的一致即可。客户端的不确定性包括浮点数运算、操作系统、算法、第三方库等等。

基于实现不同,两种同步模型在某些方面的特征

虽然在一些方面表现出了差异,但是关于哪类游戏应该选用哪种同步模型,除了一些对某些要求比较极端的游戏类型更适合哪种模型之外(比如对实时性有着极强要求的格斗类游戏(FTG)适合使用锁步同步,而有大量玩家同时在线的MMORPG适合选用状态同步),没有严格的选用标准。一般围绕着核心玩法、安全性、开发维护成本等方面进行评估。

而作为一个单局PVP为主的FPS游戏,单局内可观察的网络对象较少,网络流量负担较小,而PVP要求保证公平性,以服务器的算结果作为权威,在安全性有更大优势的状态同步更为合适,且没有客户端不确定性的风险,开发负担更小,版本质量更加可控。

二、TCP or UDP

为大家所熟知的,TCPUDP主要有以下几个特点 TCP:

  1. 基于连接的
  2. 可靠保序的
  3. 自动分包的
  4. 有流量控制

UDP:

  1. 无连接的
  2. 不保序、不可靠的
  3. 需要手动分包

一眼看上去,似乎tcp是最佳的选择,拥有我们想要的所有东西,使用起来也非常方便,但正是因为它部分强大的功能,在某些情况下,特别是在弱网情况下,会对网络的实时性会造成严重的影响。其中包括

  1. TCP默认会开启Nagle算法。Nagle算法的实现是:数据只有在写缓存中累积到一定量之后,才会被发送出,通过减少需要传输的数据包数量,来优化网络,这将会造成一定的延时。虽然可以通过设置TCP_NODELAY的选项来关闭这个算法功能。
  2. 实现可靠保序的方式。TCP为我们提供了可靠保序的保证,但对于时效性强的数据来说,这个代价过于巨大。当一个包丢失时,接收方会无法获取后续到达的包,直到收到这个包为止(延迟到达或是重传)。对于一次丢包而言,超时重传时间大约是,再加上发送到接收的,所以丢了一个包就会有接近3个RTT的延迟。而状态同步很多时候与其收到每一个包,收到最新的包反而是更重要的,有些包丢掉也没有关系,比如后续新的状态会直接覆盖掉之前的状态,而包含状态的包即使被丢掉也没有关系。
  3. 拥塞控制算法,包含了慢开始、拥塞避免、快速重传、快速恢复,慢启动就是连接刚建立,一点一点地提速,试探一下网络的承受能力;拥塞避免算法可以避免窗口增长过快导致窗口拥塞,而是缓慢的增加调整到网络的最佳值。两者都是为了实现更好的公共网络环境,而牺牲了一些自己的网络性能。

以上TCP影响实时性的几点特性,除了1可通过配置避免之外,其他两个都是无法做出调整的,这导致了相对于UDP,TCP的传输速度慢很多,特别是在弱网的情况下,会大大影响游戏的体验。所以对网络实时性有要求的网络游戏,基本都采用UDP作为传输的协议,再根据需要,基于UDP开发一套可靠的协议。

三、基于UDP开发的协议

其实选用UDP的原因只是TCP的那几个严重影响实时性的功能无法关掉而已,而TCP关于连接的概念、可靠保序的实现方式等都是值得借鉴的。下面是在UDP之上实现的一套协议

1. 连接

连接可以有多种状态,比如开始连接、连接中、断开连接等,通过这些状态,我们可以知道客户端和服务器的交互情况,玩家是否正常游戏。

  1. 连接的标识,需要在UDP的基础之上建立一个连接的概念,标识一个唯一连接的方式有很多,比如客户端请求的IP和Port。而对单局,则使用全局玩家唯一标识id作为连接的标识,单个玩家只可建立一个连接。
  2. 连接的建立,客户端发送建立连接的请求,服务器校验通过后,返回确认包,此时服务器认定已经成功建立连接,客户端收到Ack后确认建立了连接,如果Ack包丢了,客户端重发请求,服务器收到后再次回Ack。
  3. 建立的校验,服务器的连接资源是有限的,为了防止恶意连接,需要对建立连接的请求进行校验。可选用的一种简单方式是,服务器收到请求后,给客户端发一个验证包,客户端需要某种操作之后再将结果包回给服务器,连接才会建立。简单的做法是,在玩家开局之后,room就会请求pvp为每一个guid预先建立一个connection,只有拥有该connection的id才可以建立.
  4. 连接成功后,客户端可发送数据包,服务器在tick中处理处理每一个包并进行统计记录。
  5. 客户端的主动断开,客户端发出断开的请求,服务器设置标志位让出资源,并上报该链接的全局统计结果。
  6. 数据加密,简单的做法是对包内容的加密只是做了简单的异或处理,每个连接都会有一个密钥,将密钥与数据按照某种规则进行异或的操作。

2. 可靠性

2.1 QOS

根据对可靠性的不同需求,一般会实现不同可靠程度的通信通道(channel),包括

  1. 不可靠不保序通道
  2. 不可靠保序通道
  3. 可靠保序通道

只实现了不可靠保序和可靠保序。(不可靠不保序应用价值不大?) 两者的实现都是基于数据包Package的序列号Seq实现的,每个channel记录了两个seq : 1.当前channel的从socket收到的最大的seq(last_recv_seq_);2.当前channel已经处理的数据包的seq(recv_read_seq_)。在上层从调用接口(ReadData)读取缓冲区数据时,两者的处理方式不同

  1. 不可靠保序,读取缓冲区内从recv_read_seq_到last_recv_seq_的所有数据包,如果包未到达则算丢失,不重传。
  2. 可靠保序,只读取recv_read_seq_的包,如果该包未到达,则直接返回,直到该包到达为止,具有超时重传和快速重传策略。
  3. 如果实现不可靠不保序的话,应该直接读取当前缓冲区内所有的包,再根据需要制定判定丢失的规则,不重传。
2.2 重传机制

重传的策略有两个,超时重传+快速重传。 数据包触发重传的条件有两个:

  1. 到达超时时间,即 Now < send_time + RTO ,其中RTO的参考计算公式为

DevRTT是Deviation RTT。在Linux下,α = 0.125,β = 0.25, μ = 1,∂= 4 。一般为了防止极端情况下rto过大或过小,可以对rto的值进行”钳位“。

  1. 比此包更新的数据包已被确认,且此包发送次数小于等于1时(即最多发过1次),也会立刻触发一次重发。

3.包的结构

3.1 数据包头PkgHead

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct PkgHead {
    int seq;  // 超时重传依据该seq
    ...
}

3.2 网络包包头DataHead

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct DataHead {
    int cmd;  // ACK确认以及RTT等统计依据该seq,网络包不会重传,网络包的内容可以是重传的包
    ...
    int last_ack;
    int ack_bits;  // 服务器已收到的客户端包的最近的32个序列(即是服务器的ack序列),大于32即算丢包
    ...
}

4.数据压缩

对网络包的数据字段进行了压缩,压缩算法采用LZ4。 https://lz4.github.io/lz4/ 从其github上所贴出的测试数据可以看出,该压缩算法在压缩、传输和解压的综合性能上相比其他算法还是比较优秀的。对服务器性能的影响较小,且能节约带宽。

5.加密

加密采用了简单的异或操作,将压缩后的包体根据特定规则生成的密钥进行异或操作。密钥由单局分配connection时进行生成,并通过开始单局的ntf下发给客户端

REF

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-11-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
RabbitMQ 消息队列
RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统。他遵循Mozilla Public License开源协议。
py3study
2020/01/16
9630
python操作rabbitmq 实践笔
2.  实现功能: (1)rabbitmq循环调度,将消息循环发送给不同的消费者,如:消息1,3,5发送给消费者1;消息2,4,6发送给消费者2。                    (2)消息确认机制,为了确保一个消息不会丢失,RabbitMQ支持消息的确认 , 一个 ack(acknowlegement) 是从消费者端发送一个确认去告诉RabbitMQ 消息已经接收了、处理了,RabbitMQ可以释放并删除掉了。如果一个消费者死掉了(channel关闭、connection关闭、或者TCP连接断开了)而没有发送ack,RabbitMQ 就会认为这个消息没有被消费者处理,并会重新发送到生产者的队列里,如果同时有另外一个消费者在线,rabbitmq将会将消息很快转发到另外一个消费者中。 那样的话你就能确保虽然一个消费者死掉,但消息不会丢失。         这个是没有超时的,当消费方(consumer)死掉后RabbitMQ会重新转发消息,即使处理这个消息需要很长很长时间也没有问题。消息的 acknowlegments 默认是打开的,在前面的例子中关闭了: no_ack = True . 现在删除这个标识 然后 发送一个 acknowledgment。                    (3)消息持久化,将消息写入硬盘中。  RabbitMQ不允许你重新定义一个已经存在、但属性不同的queue。需要标记消息为持久化的 - 要通过设置 delivery_mode 属性为 2来实现。         消息持久化的注意点:         标记消息为持久化并不能完全保证消息不会丢失,尽管已经告诉RabbitMQ将消息保存到磁盘,但RabbitMQ接收到的消息在还没有保存的时候,仍然有一个短暂的时间窗口。RabbitMQ不会对每个消息都执行同步 --- 可能只是保存到缓存cache还没有写入到磁盘中。因此这个持久化保证并不是很强,但这比我们简单的任务queue要好很多,如果想要很强的持久化保证,可以使用 publisher confirms。                    (4)公平调度。在一个消费者未处理完一个消息之前不要分发新的消息给它,而是将这个新消息分发给另一个不是很忙的消费者进行处理。为了解决这个问题我们可以在消费者代码中使用 channel.basic.qos ( prefetch_count = 1 ),将消费者设置为公平调度。 生产者
py3study
2020/01/09
2K0
Docker中部署RabbitMQ并使用Python3.x操作全书(Python操作RabbitMQ看这一篇就够了)
使用Python操作RabbitMQ的书籍以及例子,少之又少。翻遍了网上所有的例子,发现十个有9个半不能运行的,这半个你还得修改。 原因很简单,要么例子的Python版本太低了,要么例子的RabbitMQ的版本太低了。所以造成了一系列文字。 让我很痛苦,决定下笔写一篇关于这个的文章。
手撕代码八百里
2020/07/28
1.7K0
Docker中部署RabbitMQ并使用Python3.x操作全书(Python操作RabbitMQ看这一篇就够了)
消息中间件工作队列 — RabbitMQ
工作队列(又称:任务队列——Task Queues)是为了避免等待一些占用大量资源、时间的操作。当我们把任务(Task)当作消息发送到队列中,一个运行在后台的工作者(worker)进程就会取出任务然后处理。当你运行多个工作者(workers),任务就会在它们之间共享。这个概念在网络应用中是非常有用的,它可以在短暂的HTTP请求中处理一些复杂的任务。
木野归郎
2020/11/25
4270
消息中间件工作队列 — RabbitMQ
RabbitMQ在Python中的使用详解
https://github.com/Coxhuang/python-rabbitmq
Autooooooo
2020/11/09
4.7K0
RabbitMQ在Python中的使用详解
Python RabbitMQ
RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统。他遵循Mozilla Public License开源协议。
py3study
2020/01/09
4830
Python RabbitMQ
Python之RabbitMQ
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件。RabbitMQ服务器是用Erlang语言编写的,它可以为你的应用提供一个通用的消息发送和接收平台,并且保证消息在传输过程中的安全,RabbitMQ官网,RabbitMQ中文文档。
py3study
2020/01/06
7270
Python之RabbitMQ
Python3 通过 pika 连接 R
【RabbitMQ 服务器】 # 在 vhosttest 里面有 exchangetest 和 queuetest 通过 rkeytest 绑定 Broker: 192.168.0.xx virtual host: vhosttest Exchange: exchangetest  Queue: queuetest  Routing key: rkeytest 【Python 环境】 OS: Windows 10 Python: 3.6.3 x64 pika: 0.11.2 【查看队列状态】 # 通过浏览
py3study
2020/01/03
8740
【说站】python rabbitmq是什么
1、当信息在一个队列中变成死信时,可以重新发送到DLX,绑定DLX的队列称为rabbitmq。
很酷的站长
2022/11/24
3020
pika.exceptions.ConnectionClosed 问题
最近一个处理程序又遇到 pika.exceptions.ConnectionClosed 这个问题,
周小董
2019/03/25
2.9K0
pika.exceptions.ConnectionClosed 问题
python【第十一篇】消息队列RabbitMQ、缓存数据库Redis
  AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。   AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。   RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。   下面将重点介绍RabbitMQ中的一些基础概念,了解了这些概念,是使用好RabbitMQ的基础。
用户1432189
2018/09/05
2.3K0
python【第十一篇】消息队列RabbitMQ、缓存数据库Redis
rabbitmq redis
coders
2018/01/04
7930
rabbitmq redis
python中RabbitMQ的使用(安装和简单教程)
RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现的产品,RabbitMQ是一个消息代理,从“生产者”接收消息并传递消息至“消费者”,期间可根据规则路由、缓存、持久化消息。“生产者”也即message发送者以下简称P,相对应的“消费者”乃message接收者以下简称C,message通过queue由P到C,queue存在于RabbitMQ,可存储尽可能多的message,多个P可向同一queue发送message,多个C可从同一个queue接收message
周小董
2019/03/25
3.9K0
python中RabbitMQ的使用(安装和简单教程)
部署Rabbitmq
RabbitMQ是一个开源的靠AMQP协议实现的服务,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。 AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。 它可以使对应的客户端(client)与对应的消息中间件(broker)进行交互。消息中间件发布者(publisher)那里收到消息(发布消息的应用,也称为producer),然后将他们转发给消费者(consumers,处理消息的应用)。由于AMQP是一个网络协议,所以发布者、消费者以及消息中间件可以部署到不同的物理机器上。
小手冰凉
2020/04/02
6470
Python操作Rabbit MQ的5种
官网参考文档: http://www.rabbitmq.com/getstarted.html
py3study
2020/01/19
8130
Python操作Rabbit MQ的5种
mac安装rabbitmq及python的简单连接
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。 RabbitMQ服务器是用Erlang语言编写的,而集群和故障转移是构建在开放电信平台框架上的。 所有主要的编程语言均有与代理接口通讯的客户端库。
李玺
2021/11/22
6610
mac安装rabbitmq及python的简单连接
Python之Rabbitmq的fanout模式
这种模式下,传递到 Exchange 的消息将会转发到所有与其绑定的 Queue 上。
Wu_Candy
2022/07/04
4340
Python之Rabbitmq的fanout模式
RabbitMQ(从安装到使用)
  RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统。他遵循Mozilla Public License开源协议。
Wyc
2018/09/11
5370
RabbitMQ(从安装到使用)
python测试rabbitmq的消息收
send.py #!/usr/bin/env python    # -*- coding: UTF-8 -*-  import pika   import random            credentials = pika.PlainCredentials('root', '123456')   #这里可以连接远程IP,请记得打开远程端口     parameters = pika.ConnectionParameters('139.x.x.x',5672,'/',credentials)    
py3study
2020/01/08
1.1K0
Python自动化开发学习11-Rabb
好了,这样就安装完了。 其实,rabbitmq是用erlang语言实现的,这里用yum安装,把有依赖关系的erlang也一起安装好了。
py3study
2020/01/07
4550
相关推荐
RabbitMQ 消息队列
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验