游戏服务端基础

同步算法

  1. 指令->状态 同步:
    • players用于保存战场中的所有角色,角色的属性x和y代表它的位置坐标,speedx和speedy代表移动方向,服务端每个0.2秒调用一次move_update,该方法会计算角色的新坐标,然后广播移动协议。–收到客户端移动指令
      function 0n_shift_msg(player,msg)
      player.speedx = msg.x
      player.speedy = msg.y
      end

      --每隔0.2秒调用一次
      function move_update()
      for i,v pairs(players) do
      v.x = v.x + v.speedx * 0.2
      v.y = v.y + v.speedy * 0.2
      if v.speedx ~= 0 or v.speedy ~= 0 then
      local msg = {"move" , v.playerid , v.x , v.y}
      broadcase(msg)
      end
      end
      end
  2. 客户端障眼法:虽然网络延迟不可避免,但可以在客户端用障眼法,提高游戏体验
    • 插值算法:
      • 客户端收到移动协议后,不会直接设置角色坐标,而是让角色慢慢往目标点移动,服务端在0秒、0.2、0.4分别发送3条移动协议,告知角色在ABC三个点,当客户端收到B时,不直接设置,而是让角色慢慢走向B点,收到C时慢慢走到C,使用插值算法,就算是以0.2秒一次的低频率同步,玩家也能有较好的体验。
      • 障眼法代价,插值算法比直接设置位置存在更大误差,客户端本来0.4秒到B点,插值算法需要0.6秒到,增加了0.2秒延迟。比起直接设置位置,还是插值算法更好,虽然有0.2秒的误差。
  3. 缓存队列:
    • 单纯的插值算法不能解决顿挫问题,比如A到B花费的时间为0.2秒,B到C较长的距离花费的时间也是0.2,移动速度发送突变,也会影响玩家的体验。
    • 客户端可以通过缓存队列来缓解速度跳变的问题,比如,收到移动协议后,不立即处理,而是把协议数据存在队列中,再用固定的频率每个0.2秒取出,结合插值算法移动角色,缓存队列相当于是在客户端加一层缓存来缓解网络抖动问题,可以很好的提高游戏体验。
    • 客户端还可以动态调整取出的速率,当队列里积累了较多数据时,可以稍微加快,较少时可以稍微减缓,从而可以更好地抵抗网络抖动,但比起单纯使用插值算法,缓存队列代价是误差更大。
    • 结合插值算法和缓存队列,单靠客户端的优化就能够解决大部分同步问题,对于误差敏感比如fps,可以通过主动方优先的策略来提高玩家的游戏体验。
  4. 主动方优先:
    • 插值算法、缓存队列会加大不同玩家所见画面的差异,差异越大打不中、莫名被打死的问题就很可能发生,对于这种问题一般采取三种策略。
      • 不管客户端的误差,一切以服务器计算为准。
      • 信任主动方,客户端 A 发送击中B的协议,只要偏差不是太大(距离远)服务端就认定A击中了B,这种方法可以提高A的游戏体验,但B可能会感到莫名被打死。
      • 信任被动方:客户端B发送击中A的协议,这种可以提高B的游戏体验,但A可能感到瞄准了打不中的感觉。
      • 虽然误差还是无法避免,但可以通过主动方策略进行应对,提高游戏体验。
  5. 各类同步方案及适用场景
    • 根据服务端的输入输出内容,同步方案分为三大类,指令->指令、指令->状态、状态->状态,游戏中可能采取组合方案。方案客户端运算能力服务端运算能力状态->状态√客户端运算,服务端转发,优点:即时表现,玩家体验好 缺点:容易作弊+校验能力指令->状态+先行表现√服务端运算 优点:有效杜绝作弊 缺点:服务端负载压力大指令->指令√帧同步 优点:可以同步大量角色 缺点:容易错乱+校验能力
    • 适用场景:游戏类型最适用方案原因射击(FPS)状态->状态对实时性要求高,玩家打出一枪后期待马上看到效果,用客户端运算方案,能给玩家即时反馈,体验感更好。由于同一个场景的玩家可能有很多,因此不适合采用帧同步的方案。即时战略(RTS)指令->指令(帧同步)需要同步大量单位,如果使用状态同步,则意味着需要同步大量数据,而指令->指令只需要同步小部分的操作指令。竞技 (MOBA)指令->指令(帧同步)对公平性要求较高,即对误差的要求较高。原理上就是帧同步可以保证多个客户端的误差很小。角色扮演(MMORPG)指令->状态同一个场景的角色较多,不适合帧同步,如果需要较好地防止作弊,最好选用 指令->状态的方案。开房间休闲类(如:球球大作战)指令->状态 或 指令->指令如果实时性要求较低,但对防作弊的要求高,则适合使用指令->状态的同步方案。如果需要同步大量单位,比如球球大作战基础上加个食物飘动的功能,则适用帧同步方案。
    • 有时需要结合多种同步方案,比如ARPG使用 状态->状态 的方案同步角色位置,使用 指令->指令 的方案同步技能,使服务端具备一定的反作弊能力
  6. 帧同步
    • 严格帧同步:严格帧同步(localstep)是一种确保所有玩家在同一帧内操作的同步方式。在这种模式下,每个玩家的延迟都等于延迟最高的那个人。这意味着,如果有任何玩家的网络延迟较高,所有其他玩家也必须等待,以确保所有操作在同一帧内完成。严格帧同步适用于网络环境很稳定且延迟较短的场景。如果网络不好,客户端频繁等待,游戏体验会极差。
    • 乐观帧同步:乐观帧同步则采用“定时不等待”的乐观方式,以一定时间内的不一致性换取体验。在乐观帧同步中,服务端定时广播操作指令以推进游戏进程,而不必等待慢客户端。如果客户端有延迟,服务端不会等待,而是让延迟的客户端缓存指令并追赶执行。这种方式的广播策略比严格帧同步简单,因此大多数帧同步都是乐观帧同步
  7. AOI算法
    • AOI算法是一种用于确定和更新游戏中玩家“感兴趣区域”的算法。在多人游戏中,玩家通常只需要感知其周围一定范围内的其他玩家,而不需要感知全部玩家。
    • 特点:通过减少不必要的数据传输和服务器处理,提高游戏的性能和响应速度,灵活性强,可以根据游戏的具体需求调整网格的大小和形状。
    • 九宫格法:是一种常见的AOI算法,能通过较低的计算复杂度寻找角色周围的实体。
      • 先把游戏场景划分成一个个的小格子,格子尺寸依照客户端一屏能看到的范围而定,每个格子都保存了其区域的实体id。
  8. 可靠UDP
    • KCP:是一个快速且可靠的传输层协议,它基于UDP协议实现,并增加了可靠性传输的功能。KCP的设计目标是提供比TCP更快的数据传输速度,同时保持数据的可靠性和有序性。(这个协议比TCP多浪费10%~20%的带宽代价,换取了平均延迟降低30%~40%)
      • 高速传输:KCP通过优化拥塞控制算法、减少数据包头开销、以及支持多路复用等方式,实现了比TCP更高的传输速度。
      • 可靠性:KCP通过确认与重传机制、序列号与顺序控制等技术,保证了数据的可靠传输。当数据包丢失或乱序时,KCP能够自动检测和恢复。
      • 低延迟:KCP具有较低的传输延迟,适用于对实时性要求较高的应用场景,如在线游戏、实时音视频传输等。
      • 抗弱网:KCP在网络条件较差时仍然能够保持较高的传输效率,对丢包、延迟抖动等情况具有较强的抵抗能力。
      • 灵活配置:KCP允许用户根据实际需求调整各种参数,如窗口大小、重传次数、超时时间等,以适应不同的网络环境和应用场景。
      • 易于集成:KCP以库的形式提供,可以方便地集成到各种应用中,无需修改底层网络协议栈。
      • 多路复用:KCP支持在一个UDP连接上同时传输多个数据流,提高了网络资源的利用率。
    • QUIC:由谷歌推出的可靠UDP
    • ENet:是一个相对轻量、简单可靠的UDP通讯库
    • RakNet:是一个基于UDP的网络库,起初是为游戏开发而设计的
    • UDT:基于UDP的可靠网络协议
  9. 缺点:
    • 服务端每隔0.2秒计算一次位置,玩家会有明显的顿挫感,瞬移等。
    • 如果将同步频率提高到24帧 0.04秒调用一次,理论上会让顿挫感消除,但同步频率过高服务端会带来性能压力,无法达到预期效果。

热更新:

  1. 概念:是一种在不重启应用或服务的情况下,更新正在运行的应用或服务的软件代码、配置或资源的技术。
  2. 独立虚拟机热更新(skynet提供的热更新方案1)
    • skynet实现了Actor模型,还为每个Lua服务开启了独立的虚拟机,这种架构为skynet提供了一些热更新的能力。
      • 可以理解为:开启新服务时,虚拟机需要重新加载Lua代码,所有只要先修改Lua代码,再重启(或创建)服务,新开的服务就会基于新代码运行,实现热更新。
      • 直接修改代码并不起作用,因为skynet使用了修改版的Lua虚拟机,它会缓存代码。所以还需要登录调试控制台(debug_console)执行清除缓存的指令(clearcache)。
      • 对于一些休闲类手游,在线的时间不会很长,可以让已在线的玩家按照旧规则玩coin+1,同时让新上线的玩家coin+2
      • 热更新示例:
        • 修改代码,coin+1 改成 coin+2,主服务main添加 skynet.newservice(“debug_console”,8000) 开启debug_console服务,让它监听8000端口
        • 登录调试控制台 telnet 127.0.0.1 8000 执行 clearcache 指令
    • skynet独立虚拟机热更新的方式适合在 一个客户端对应一个代理服务 的架构下热更新代理服务,以及在开房间型游戏中热更新战斗服务。
    • 虽然旧客户端依然是旧代码,但重新登录就能运行新的版本。
    • 旧比赛也是这个原理,新开的比赛运行新版本,程序最终会趋向于新版本。
  3. 注入补丁(skynet提供的热更新方案2)
    • skynet还提供了一种称为 inject(注入)的热更新方案,就是写一份补丁文件,把它注入某个服务,就可以单独修复这个服务的bug。
    • 适用场景:注入热更新适用于紧急修复bug的情况,补丁文件写法比较诡异,容易出错,还有就是Lua调试模块debug的运行效率较低。
  4. 利用网关实现热更新
    • 利用网关也可以实现优雅的进程切换,客户端与网关连接,网关再将消息转发给逻辑进程,需要热更新时,开启一个新的逻辑进程,让旧连接请求转发到旧进程,新连接转发到新逻辑进程,等所有旧连接处理完,关闭旧逻辑进程。由于引入了网关,因此切换进程过程中,客户端不会中断,从而实现了热更新。
  5. 热更新底层
    • 一般通过多个进程切换配合,需要做到进程级别的无状态,或者能够在重启时恢复整个进程的状态,来实现热更新的,像nginx,输入热更新指令,内部会调用fork和exec函数,开启一组新版本进程,旧连接由旧进程负责、新连接由新进程负责,在旧进程处理完连接后,用户可以输入指令让它退出。
    • fork函数:调用fork函数进程可以复刻自己,而且是从调用fork函数开始执行
    • exec函数:则提供一个在进程中启动另外一个程序执行的方法
    • 还有一种是动态库热更新,是指把某些变量和方法编写到外部的动态库文件(.so)中,在程序运行时再动态加载它们,这种方式可以用于热更新动态库中的内容,只需要把动态库替换即可。
  6. 进程切换、动态库、脚本语言热更新演化过程
    1. 进程1->进程2
    2. 进程->a.so->b.so
    3. 最后到脚本语言
  7. lua实现热更新
    1. 动态加载模块: 使用Lua的require函数动态地加载模块。可以调用require来重新加载,标准的require实现会缓存已加载的模块,需要自定义一个加载器或使用package.loaded来手动管理已加载的模块。
    2. 使用沙箱(Sandbox): 创建一个或多个Lua沙箱环境,允许你在不中断主程序的情况下加载和执行新的代码。这通常涉及使用lua_newthreadlua_newstate来创建新的Lua状态机,并在其中加载和执行你的代码。

防外挂

  1. 不信任客户端
    • 就是对客户端传来的数据进一步校验,服务器在接收到这些操作后,进行校验,并基于校验结果执行相应的业务逻辑。
  2. 尽可能多的校验
    • 服务端应做可能多的校验,校验越多,作弊的难度越大,
    • 比如像跑酷游戏,防止玩家刷金币,由服务端产生一局所有金币信息,包含坐标,是否被角色吃掉,每个金币都有随机key,可以提高作弊。更好的判断金币是否存在,是否吃掉,角色是否在金币附近,金币的随机key是否对应上。
  3. 反外挂常用措施
    • 服务端进行尽可能多的校验,能提高作弊难度,此外,客户端也应做加壳、加密保护。
    • 封包过滤:通过封包过滤技术,对游戏数据包进行检测和过滤,过滤掉非法的封包。对常见的外挂软件的网络通信进行监控,找到其特征信息,并进行封包过滤。
    • 加密和验证:使用加密算法对关键数据进行加密,如玩家的游戏数据、连接信息等。对玩家的身份进行验证,通过账号系统、IP地址校验等方式,确保只有合法的玩家可以进入游戏。
    • 内存注入检测:监控游戏进程的内存操作,检测是否有外挂程序注入到游戏进程中。根据外挂程序的运行特征,如频繁读取/写入内存、修改游戏变量等,进行检测和封锁。
    • 客户端和服务器的双重验证:在游戏中使用双重验证机制,通过验证客户端和服务器之间的数据完整性来检测外挂软件或作弊行为。
  4. 防外挂的核心要点,就是要尽可能多地让服务端做逻辑运算、尽可能多地校验客户端的运算结果。

高并发

  1. Actor模型
    • actor模型把“执行任务”抽象成了各服务消息队列里的元素,从而使得CPU的各线程能够并行处理这些任务,以提高效率。
  2. 服务端架构:
    • 大世界架构:该架构把整个服务端分成网关、游戏服务、登录服务、中心服务、全局服务等几大部分,各个部分可以用一个单独的进程或一个Actor来表示,该架构具有较强的通用性,适用于MMORPG、卡牌游戏、开房间类型等。
    • BigWorld:它把所有事物都归结为实体和空间两大类。
    • 无缝大地图:
    • 滚服架构:

负载均衡

  • 概述:负载均衡它用于将网络请求或工作负载分散到多个服务器或网络设备上,以优化资源使用、提高系统性能、增强可靠性。在游戏服务端中,负载均衡的作用是将游戏玩家的请求分发到各个服务器上,使服务器能够协同工作,避免单个服务器过载,从而提高整个系统的处理能力和稳定性。
  • 游戏服务端实现负载均衡的策略:
    • 静态负载均衡策略:在游戏服务器启动时,根据服务器的处理能力和配置情况,预先分配一定的游戏玩家请求数量。这种策略适用于玩家数量相对固定的场景,但当游戏玩家数量波动较大时,效果可能不理想。
    • 动态负载均衡策略:根据服务器的实时负载情况,动态地调整游戏玩家请求的分配。这种策略能够更好地利用服务器的处理能力,常见的动态负载均衡算法包括轮询、哈希、最少连接等。
      • 轮询算法:将游戏玩家的请求按照一定的顺序分配给各个服务器,当某个服务器的请求数量达到一定阈值时,就将其排除在外,不再接收新的请求。
      • 哈希算法:将游戏玩家的请求通过哈希函数分配给各个服务器,保证相同的请求会被同一个服务器处理。
      • 最少连接算法:将游戏玩家的请求分配给当前连接数最少的服务器,以实现负载的均衡。
  • 负载均衡和分布式的区别:
    • 分布式定义:分布式系统是指将系统中的组件分布在不同的计算机或节点上,通过网络进行通信和协作,共同完成某项任务或提供某种服务。
    • 负载均衡技术可以帮助分布式系统实现资源的均衡利用和故障转移,从而提高系统的性能、可靠性和可伸缩性。而分布式系统则通过多个独立节点的协同工作,实现更高级别的性能和可靠性。在实际应用中,负载均衡和分布式技术常常结合使用,以构建高效、稳定、可扩展的系统架构。
    • 目标:
      • 负载均衡:目标是优化资源使用、提高系统性能、增强可靠性。
      • 分布式:目标是实现系统的可扩展性、高可用性和容错性。
文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇