欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 游戏 > GAMES104:18 网络游戏的架构基础-学习笔记

GAMES104:18 网络游戏的架构基础-学习笔记

2025/2/25 8:34:43 来源:https://blog.csdn.net/yx314636922/article/details/143162026  浏览:    关键词:GAMES104:18 网络游戏的架构基础-学习笔记

文章目录

  • 课前QA
  • 一,网络协议Network Protocols
    • 1.0 Socket
    • 1.1 传输控制协议TCP(Transmission Control Protocol)
    • 1.2 用户数据报协议UDP(User Datagram Protocol)
    • 1.3 Reliable UDP
      • 1.3.1 自动重传请求ARQ(Automatic Repeat Request)
        • 1.3.1.1 滑窗协议Sliding Window Protocol
          • stop-and-wait ARQ
          • Go-Back-N ARQ
          • Selective Repeat ARQ
      • 1.3.2 FEC(Forward Error Correction)
        • 1.3.2.1 异或校验位XOR FEC
        • 1.3.2.2 Reed-Solomon Codes
  • 二,时钟同步Clock Synchronization和RPC(Remote Procedure Call)
    • 2.1 时钟同步
    • 2.2 RPC(Remote Procedure Call)
      • RPC
  • 三,网络拓扑Network Topology
    • 3.1 Peer-to-Peer(P2P)
    • 3.2 Dedicated Server专用服务器
  • 四,游戏同步Game Synchronization
    • 4.1 快照同步snapshot Synchronization
    • 4.2 帧同步lockstep Synchronization
    • 4.3 状态同步state Synchronization
  • QA


网络游戏和单机游戏相比有很多难点,比如说如何保证每个玩家的游戏状态是一致的、如何进行网络同步、如何处理延迟和丢包、如何检测玩家的作弊行为、如何处理不同的设备和系统、如何设计高性能的服务器系统、如何热更新等等。这些网络游戏专有的问题都为游戏引擎的开发提出了更大的挑战。

所以网络游戏是一个非常复杂庞大的系统,在这一部分我们同样分为两节来介绍相关的技术。

课前QA

  • 怎么看深度学习和强化学习的未来趋势?
    现在还是有监督学习多一些,openai增强学习多一点(课程在2022年),但心态保持开放性和实用主义,哪个有用用哪个。比如在游戏ai里经常使用简单ai就可以解决问题。

  • ai模块是否要跟随游戏进行版本更新?
    除非决策行为或者游戏state发生变化,否则ai原则上可以不更新。

  • DLSS是什么
    基于深度学习的超采样。可以用720p的数据还原4k画质,原理与“老照片修复”等ai相似,除了采样原帧数据,还会采样时域数据,最终通过生成来完成超采样过程。(课程时间是dlss2.0)并且不需要为特定场景去调整算法。

并且老师认为未来的渲染趋势就是这样的“非逐像素”渲染才对。


  • 网络游戏和单机游戏相比有很多难点,比如说:
  1. 如何在大量玩家里保证每个玩家的游戏状态是一致的、如何进行网络同步
  2. 如何处理延迟、丢包和重连
  3. 如何检测玩家的作弊行为
  4. 如何处理不同的设备和系统
  5. 如何设计高性能的服务器系统等等。

这些网络游戏专有的问题都为游戏引擎的开发提出了更大的挑战。

  • 课程大纲:
    在这里插入图片描述

一,网络协议Network Protocols

在介绍网络游戏相关的内容前我们先来介绍一下网络协议(network protocols)。

网络协议要解决的核心问题是如何实现两台计算机之间的数据通信,当软件应用和硬件连接不断地变得复杂时直接进行通信是非常困难的。因此人们提出了中间层(intermediate layer)的概念来隔绝掉应用和硬件,使得开发者可以专注于程序本身而不是具体的通信过程。
在这里插入图片描述

在现代计算机网络中人们设计了 OSI分层模型来对通信过程进行封装和抽象。如果对这七层协议了解的话,可以发现有些冗余的地方还可以优化。
在这里插入图片描述

1.0 Socket

对于网络游戏开发来说一般不需要接触到很底层的通信协议,在大多数情况下只需要知道如何使用 socket 建立连接即可。

socket 是一个非常简单的结构体,我们只需要知道对方的 IP 地址和端口号就可以使用 socket 建立连接来发送和接收数据。

建立连接时需要额外注意到底是使用 IPv4 还是 IPv6,或者都需要;使用 TCP 还是 UDP 协议等问题。

1.1 传输控制协议TCP(Transmission Control Protocol)

TCP 是最经典也是著名的网络协议,它连接牢靠、可以按顺序接收、还可以进行流量控制(网络差时可以减低发包)。

  • 原理核心: retransmission mechanism。这个机制要求接收端收到消息后会向发送端发送一个 ACK(告知),而发送端只有接收到这个 ACK 之后才会继续发包,如果有意外没有收到则会反复发最新的。
  • 拥塞控制(Congestion Control):当 TCP 出现网络拥堵时会主动调节单次发包的数量。如果发包都能顺利地接收到则会提高发包数量以提升效率,反之则会减少发包数量以提升稳定性。这样能有效保证服务器不被堵死,但因此也会造成带宽不稳定,这对游戏来说不太友好。

1.2 用户数据报协议UDP(User Datagram Protocol)

UDP的发明者也是TCP的发明者之一,本质是一个轻量级的端到端的协议。UDP的特点是:

  1. 不需要握手(长时间连接),发就发了,收不收随你;
  2. 不需要稳定也没有顺序;
  3. 也不管流量控制

因为简单,所以UDP的包头只有4byte(TCP有20byte),具体对比见下图
在这里插入图片描述

在现代网络游戏中根据游戏类型的不同使用合适的网络协议,比如说对于实时性要求比较高的游戏会优先选择UDP(比如CSGO),而策略类、时间不敏感的游戏则会考虑使用TCP(比如炉石)。在大型网络游戏如MMO中还可能会使用复合类型的协议来支持游戏中不同系统的通信需求,比如登录、聊天、邮件用TCP,战斗用UPD。

1.3 Reliable UDP

TCP比较复杂又比较重,虽然稳健又可靠,但带宽一会上一会下,并且前置消息收不到后续的也就都收不到,卡住了(有些游戏是可以接受丢包的);而UDP轻量但不够稳定,传没传到都不知道,起码的信任都没有。

那么有没有结合两者的可靠UDP连接呢?有,现代网络游戏中往往会基于UDP来定制一个网络协议,这样既可以利用UDP的高效性又可以保证数据通信的有序性。实际游戏制作中可以采用各种第三方协议,或者自己定制一个。

  • ACK(Positive acknowledgment)和NACK(Negative acknowledgment)技术可以保证数值可靠(返回收到XX信息和没收到xx信息)
  • 序列号SEQ(Sequence number)用于编码主机发出的每个包(TCP常用)
  • Timeouts时间过长就不管它的机制

1.3.1 自动重传请求ARQ(Automatic Repeat Request)

ARQ(automatic repeat request)是基于ACK的错误控制方法,所有的通信算法都要实现ARQ的功能。

1.3.1.1 滑窗协议Sliding Window Protocol

滑窗协议(sliding window protocol)是经典的ARQ实现方法,它在发送数据时每次发送窗口大小的包然后检验回复的ACK来判断是否出现丢包的情况。

滑窗协议的一个缺陷在于它需要等待接收端的ACK才能继续发送数据,因此在很多情况下它无法完全利用带宽。

stop-and-wait ARQ

等到ACK后再发送,但太笨了,没人这么用。

Go-Back-N ARQ

只接受ACK,没收到ACK时,会回去把包含那个包的整个窗口内的包重新发送。

Selective Repeat ARQ

不停往下传,然后只重新发送NACK(丢失或损坏的包)的包。

1.3.2 FEC(Forward Error Correction)

在网络游戏中需要额外处理丢包的问题,因此我们在自定义网络协议时一般会结合forward error correction(FEC)的方法来避免数据的反复发送。一般是空间换时间。

1.3.2.1 异或校验位XOR FEC

异或校验位是使用异或(XOR)运算来恢复丢失数据的方法。这里需要注意的是当同时有多个包丢失时,使用异或校验位是无法恢复数据的。

1.3.2.2 Reed-Solomon Codes

Reed-Solomon codes是经典的信息传输算法,它利用Vandemode范德蒙矩阵及其逆阵来恢复丢失的数据,可以覆盖丢包率高的场景,比如手游。

总结一下,在自定义UDP时需要考虑ARQ和FEC两类问题。

二,时钟同步Clock Synchronization和RPC(Remote Procedure Call)

2.1 时钟同步

玩家之间的时钟同步(clock synchronization)问题也是大型网游中的难点。

  • RTT(Round Trip Time):由于网络通信延迟的存在,客户端向服务器端发送一个包后都需要等待一定的时间才能收到回包,这个间隔的时间称为round-trip time(RTT)。RTT的概念类似于ping,不过它们的区别在于ping更加偏向于底层而RTT则位于顶部的应用层。

  • NTP(Network Time Protocol)
    从客户端发送请求然后从服务器接收一个时刻就好,这样就可以得到4个时间戳。如果我们进一步假定网络上行和下行的延迟是一致的,我们可以直接计算出RTT的时间长短以及两个设备之间的时间偏差。当然需要注意的是在实际中网络上行和下行的带宽往往是不一致的,而且网络也是不稳定的,因此这个算法也不是十分的严谨。

  • 实际上在不可靠的通信中是无法严格校准时间的。不过在实践中我们可以通过不断的使用 NTP 算法来得到一系列 RTT 值,然后把高于平均值 50% 的部分丢弃,剩下的 RTT 平均值的 1.5 倍就可以作为真实 RTT 的估计。

实际中开发网络游戏的第一步就是把时间对起来,以避免后续的很多bug

2.2 RPC(Remote Procedure Call)

  • 尽管利用socket我们可以实现客户端和服务器的通信,但对于网络游戏来说完全基于socket的通信是非常复杂的,因为:
    1. 这主要是因为网络游戏中客户端需要向服务器发送大量不同类型的消息,同时客户端也需要解析相应类型的反馈,这就会导致游戏逻辑变得无比复杂,( 比如有种攻击就是发送乱码,但是服务端不知道这个是乱码,浪费了算力去解析);
    2. 另一方面客户端和服务器往往有着不同的硬件和操作系统(客户端可能是安卓,服务器可能是linux,他们语言、数据类型大小、数据排序可能都不一样),这些差异会使得游戏逻辑更加复杂且难以调试。

RPC

因此在现代网络游戏中一般会使用 RPC(remote procedure call)的方式来实现客户端和服务器的通信。基于 RPC 的技术在客户端可以像本地调用函数的方式来向服务器发送请求,这样使得开发人员可以专注于游戏逻辑而不是具体底层的实现。

  • Interface Definition Language:在RPC中会大量使用界面定义语言IDL(interface definiton language)来定义不同的消息形式。
  • RPC stubs:在启动时通过RPC stubs来通知客户端有哪些RPC是可以进行调用的。如果调用的 RPC 不存在,那么就返回错误,但是不会让应用程序 crash。因为这些错误只会导致特定的服务没有发生,不希望它们会影响到整个应用程序,所以用 RPC stubs 预先检查调用的 RPC
  • Real RPC Package Journey:当然真实游戏中的RPC在实际进行调用时还有很多的消息处理、压缩解压缩和加密工作。

三,网络拓扑Network Topology

3.1 Peer-to-Peer(P2P)

在设计网络游戏时还需要考虑网络自身的架构。

-Original peer-to-peer是最经典的网络架构,此时每个客户端之间会直接建立通信(古早网吧局域网)。现在用的少了,一般只用于2个人点对点的进场协议一类。

  • 主机P2P:当P2P需要集中所有玩家的信息时则可以选择其中一个客户端作为主机,这样其它的客户端可以通过连接主机的方式来实现联机(机房或沙盒类游戏如魔兽红警里的房间等),但主机的电脑性能会影响大家游戏体验。

3.2 Dedicated Server专用服务器

对小型网游来说P2P可能足够了,但在现代的大型的商业网络游戏必须使用专用服务器,并且为了满足不同网络条件的玩家的需求、减少延迟,运营商可能还需要自己建立网络线路(一般直接走光缆专线,比如lol)。

四,游戏同步Game Synchronization

网络游戏需要考虑不同玩家之间的同步。由于延迟的存在,不同玩家视角下的对方可能会有不同的行为表现(比如物理解算,人物状态等)。因此我们需要使用游戏同步技术来保证玩家的游戏体验是一致的。

目前常用的同步技术包括快照同步、帧同步以及状态同步等。

4.1 快照同步snapshot Synchronization

该技术比较古老,已经基本没人用了。

其基础思想是客户端只负责发送输入到服务端,其他所有逻辑都在服务端处理,然后服务端吧整个游戏的状态生成为一个快照,再发送给每个客户端来给玩家反馈。

快照同步可以严格保证每个玩家的状态都是准确的,但其缺陷在于它给服务器提出了非常巨大的挑战。因此在实际游戏中一般会降低服务器上游戏运行的帧率来平衡性能和带宽,然后在客户端上通过插值的方式来获得高帧率。

由于每次生成快照的成本是相对较高的,为了压缩数据我们可以使用状态的变化量来对游戏状态进行表示。

快照同步非常简单也易于实现,但它基本浪费掉了客户端上的算力同时在服务器上会产生过大的压力。因此在现代网络游戏中基本不会使用快照同步的方式。

4.2 帧同步lockstep Synchronization

帧同步在现代网络游戏中非常常用。(比如王者荣耀)

不同于快照同步完全通过服务器来运行游戏世界,在帧同步中服务器更多地是完成数据的分发工作。玩家的操作通过客户端发送到服务器上,经过服务器汇总后将当前游戏世界的状态返还给客户端,然后在每个客户端上运行游戏世界。

  • Lockstep Initialization初始化

使用帧同步时首先需要进行初始化(非常重要),将客户端上所有的游戏数据与服务器进行同步。这一过程一般是在游戏loading阶段来实现的。

  • Deterministic Lockstep确定性

在游戏过程中客户端会在每一帧将玩家数据发送到服务器上,服务器接收到所有玩家的数据后再统一转发到玩家客户端上,然后由玩家客户端执行游戏逻辑。

当然这种同步方式也存在一定的缺陷,游戏进程取决于最慢的用户,当某个玩家的数据滞后了所有玩家都必须要进行等待。这种情况在一些早期的联网游戏中都很常见。

  • Bucket Synchronization

bucket synchronization中,服务器只会等待bucket长度的时间,如果超时没有收到客户端发来的数据就越过去,看下一个bucket时间段能否接收到。通过这样的方式其它玩家就无需一直等待了。(bucket synchronization本质是对玩家数据的一致性以及游戏体验进行的一种权衡)

  • Deterministic Difficulties 确定性的难点

帧同步的一大难点在于它要保证不同客户端上游戏世界在相同输入的情况下有着完全一致的输出,有了这些确定性实现,才有帧同步。

在不同客户端上要保证:

  1. 浮点数一致性:可以使用IEEE 754标准来实现,还可以用定点数(fixed)来替换浮点数
  2. 随机数一致性:将随机数种子和随机数生成算法进行同步
  3. 各种容器和算法的一致性(sort等):同步容器和算法们
  4. 数学运算函数一致性:查表法,精度都定死
  5. 物理模拟一致性:很难
  6. 代码执行顺序一致性
  • Tracing and Debugging

现代网络游戏的逻辑往往非常复杂,在玩家进行游玩时可能无法避免地出现一些bug,因此对于服务器来说检测客户端发送的数据是否存在bug就非常重要。一般来说我们会要求客户端每隔一段时间就上传本地的log(可能50帧传一次),由服务器来检查上传数据是否存在bug(自动检查,先对比所有用户log,再找到问题)。

  • Lag and Delay
  1. 为了处理网络延迟的问题我们还可以在客户端上使用buffer缓存若干帧(类似视频网站缓存),当然缓存帧越大延迟越高。
  2. 另一方面我们还可以把游戏逻辑帧和渲染帧进行分离,然后通过对渲染真插值的方式来获得更加平滑的渲染效果,是画面不会应该网络原因出现抖动。
  • Reconnection Problem断线重连

断线重连的机制:在进行帧同步时每隔若干帧会设置一个关键帧。在关键帧进会更新游戏世界的快照,这样可保证即使游戏崩溃了也可以从快照中恢复。

为了从关键帧快照取追实际队友的当前帧,可以用quick catch up技术,即暂停关闭渲染,全力执行游戏逻辑,这样可能每秒能追很多倍。

观战回放模式:而在服务器端也可以使用类似的技术,从而帮助掉线的玩家快速恢复到游戏的当前状态。实际上网络游戏的观战和回放功能也是使用这样的技术来实现的。

  • Lockstep Cheating Issues作弊问题

帧同步中,玩家可以通过发送虚假的状态来实现作弊行为,因此要有反作弊机制。例如所有的玩家都会发送校验码,通过校验码就可以找出是哪个玩家正在作弊。

但帧同步的机制本来就是你的客户端上存储了所有的游戏信息,因此还是容易出现作弊。

  • Lockstep Summary 总结
    好处:
  1. 占用带宽少,适合需要实时反馈的游
  2. 开发效率高,只要解决确定性问题
  3. 适合动作打击的游戏
  4. 方便做游戏录屏

坏处:

  1. 一致性很难保持
  2. 全图挂难以解决
  3. 需要好好设计断线重连机制

4.3 状态同步state Synchronization

状态同步是目前大型网游(比如MMORPG)非常流行的同步技术,它的基本思想是把玩家的状态和事件进行同步。

每个玩家客户端自己模拟自己的一部分世界,然后提交玩家的状态数据,服务器则会在收集到所有玩家的数据后运行游戏逻辑,模拟一个完整的整个世界,然后把下一时刻的状态按需分发给用户。(这样防作弊能力好一丁点)

  • Authorized and Replicated Clients授权和复制客户端

状态同步中服务器称为authorized server,它是整个游戏世界的绝对权威;而玩家的本地客户端称为authorized client,它是玩家操作游戏角色的接口;在其他玩家视角下的同一角色则称为replicated client,表示它们仅仅是authorized client的一个副本。(我在自己的电脑上控制自己的角色,别人看到的我的角色只是我的复制品)

  • 举个例子:
    当authorized client执行了某种行为时首先会向服务发送相关的数据,然后由服务器驱动游戏逻辑并把相应的状态发布给所有的玩家。当其他客户端接收到更新后的状态时,再驱动replicated client执行authorized client的行为。类似地,authorized client行为产生的后果也是由服务器进行计算再发布给所有的客户端。这样的好处在于我们无需要求每个客户端上的模拟是严格一致的,整个游戏世界本质上仍然是由统一的服务器进行驱动。

  • Dumb Client Problem

由于游戏角色的所有行为都需要经过服务器的确认才能执行,状态同步会产生dumb client的问题,即玩家视角下角色的行为可能是滞后的。

要缓解这样的问题可以在客户端上对玩家的行为进行预测。比如说当角色需要进行移动时首先在本地移动半步,然后等服务器传来确定的消息后再进行对齐,这样就可以改善玩家的游戏体验。(守望先锋)

由于网络波动的存在,来自服务器的确认消息往往会滞后于本地的预测。因此我们可以使用系列buffer来缓存游戏角色的状态,这样当收到服务器的消息时首先跟buffer中的状态进行检验。当buffer中的状态和服务器的数据不一致时就需要根据服务器的数据来矫正玩家状态。(怪不得有些网游在网卡的时候人物不断闪回位置)

当然这样的机制对于网络条件不好的玩家是不太公平的,他们的角色状态会不断地被服务器修正。

  • Packet Loss

对于丢包的问题在服务器端也会维护一个小的buffer来储存玩家的状态。如果buffer被清空则说明可能出现了掉线的情况,此时服务器会复制玩家上一个输入来维持游戏的运行。

  • 帧同步和状态同步两种主流同步技术的对比如下:

在这里插入图片描述

QA

  • 单纯的帧同步不能做战争迷雾吗:可以,但是容易有外挂,也许有更新的技术可以避免但老师不知道
  • 暴击可以在服务端算好再发给客户端吗:帧同步或者状态同步里,暴击大概率在服务端计算的,但状态同步里也讲过有一个预测机制,可以在同步的间隙先行计算。
  • 云游戏和快照同步有哪些区别:云游戏的架构还有很多流派的讨论,老师认为未来有可能会使用快照同步的方式,或者更狠,根本只推流视频给你

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词