当前位置: 首页 > news >正文

【Unity】uNet游戏服务端框架(三)心跳机制

更新日期:2025年10月13日。
项目源码:获取源码。

索引

  • uNet游戏服务端框架
    • 一、心跳机制
      • 1.心跳包
      • 2.服务端发送心跳包
      • 3.客户端响应心跳包

uNet游戏服务端框架

uNet游戏服务端框架为使用.Net Core开发的高性能、高并发网络游戏服务端框架(最佳适配Unity客户端),基于async / await的多线程架构天然适应IO高并发环境,使用Protobuf进行网络数据交换能够极大的降低带宽和网络延迟,同时使用对象池技术管理服务端中的大多数实例能够显著的降低GC开销。

一、心跳机制

心跳机制(Heartbeat Mechanism)是一种用于监控和维护网络应用中连接状态的技术,通过定期发送信号(心跳包)来确认系统组件之间的活跃性和可用性。

uNet中心跳包由服务端作为发送方,客户端作为响应方,在每一个心跳轮询周期内,如果客户端未在超时时间内响应心跳包,则判定为心跳超时,会被标记为已断线客户端。

目前针对已断线客户端,会被直接踢出服务器,然后客户端检测到断开连接后,自行走断线重连逻辑。

发送心跳包
响应心跳包
未响应心跳包
将客户端踢出服务器
服务端
心跳轮询,N秒
客户端
心跳超时

1.心跳包

在心跳包的设计上,必须遵循极简的策略,毕竟服务端会频繁的向所有玩家客户端发送心跳包,心跳包也无需携带任何信息,所以越小越好,哪怕设计为一个字节也可以。

uNet中心跳包即为消息数据包的校验码(大小4字节),在MMORPG服务端程序中,MMO_Player为网络玩家类,其定义的心跳包校验码如下:

namespace uNet.Example.MMO
{/// <summary>/// 玩家/// </summary>internal class MMO_Player : NetworkPlayer{/// <summary>/// 心跳包校验码/// </summary>public override int HEARTBEAT => 0;/// <summary>/// 常规信息校验码/// </summary>public override int NORMAL => 65536;}}

2.服务端发送心跳包

NetworkPlayer即维护了与一个玩家客户端的连接,所以心跳包(以及常规数据包)的发送与接收都由其来完成,在其Awake方法中:

AwakeuNet中几乎所有实例都有该方法,作为实例被唤醒时的初始化方法,由于几乎所有实例都由对象池进行管理,所以此方法的调用时机为实例创建时(从对象池中取出)调用一次。
Dispose:作为实例被销毁时的回调方法,此方法的调用时机为实例销毁时(存入对象池)调用一次。

        private static long SelfIncreasingID = 1;private static readonly object _lockObj = new object();private byte[]? _heartbeatPackage;private CancellationTokenSource? _cancel;private Socket? _socket;private TimeSpan _heartbeatInterval;private TimeSpan _heartbeatTimeout;private DateTime _lastHeartbeat;/// <summary>/// 初始化/// </summary>/// <param name="socket">Socket实例</param>/// <param name="heartbeatInterval">心跳检测间隔时间(秒)</param>/// <param name="heartbeatTimeout">心跳检测超时时间(秒)</param>public virtual void Awake(Socket socket, float heartbeatInterval, float heartbeatTimeout){//ID自增(lock防止多线程资源竞争,以防止SelfIncreasingID值异常)lock (_lockObj){ID = SelfIncreasingID;SelfIncreasingID++;}Log.LogInfo($"玩家【ID:{ID}】登入服务器!");//是否已发送心跳包(单次心跳轮询)IsSendHeartbeat = false;//是否心跳验证通过(单次心跳轮询)IsHeartbeatPass = false;//创建心跳包(也即是将心跳校验码转为字节数组即可,因为心跳包只包含一个心跳校验码)_heartbeatPackage = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(HEARTBEAT));//创建自身的异步取消Token,用于关联所有由自身启动的异步操作_cancel = new CancellationTokenSource();_socket = socket;_socket.SendBufferSize = 128 * 1024;_socket.ReceiveBufferSize = 64 * 1024;//每次心跳轮询的间隔时间_heartbeatInterval = TimeSpan.FromSeconds(heartbeatInterval);//心跳检测的超时时间_heartbeatTimeout = TimeSpan.FromSeconds(heartbeatTimeout);//注册并启动心跳检测HeartbeatTestAsync(_cancel.Token);}

在心跳检测的核心方法HeartbeatTestAsync中,使用while循环使其永久轮询(除非_cancel取消),因其为异步async方法,且使用了ConfigureAwait(false)配置,所以每次await后都会由新的空闲线程来接替,所以不用担心其性能问题:

        /// <summary>/// 心跳检测/// </summary>/// <param name="token">用于取消异步的token</param>private async void HeartbeatTestAsync(CancellationToken token){//只要token没取消,就一直轮询while (!token.IsCancellationRequested){if (!IsConnect){//如果玩家客户端已断开连接,则通过服务器入口销毁该玩家(踢出服务器)ServerEntry.Current.DestroyPlayer(this);break;}IsHeartbeatPass = false;//向玩家客户端发送心跳包(单次心跳轮询)IsSendHeartbeat = await SendDataAsync(_heartbeatPackage, token).ConfigureAwait(false);//如果发送成功(发送失败则等待进入下一次轮询,理论上不会一直失败)if (IsSendHeartbeat){//记录发送时间_lastHeartbeat = DateTime.Now;//IsHeartbeatPass = false,代表客户端还未响应心跳包while (!IsHeartbeatPass && !token.IsCancellationRequested){//判断当前是否已超时if ((DateTime.Now - _lastHeartbeat) > _heartbeatTimeout){Log.LogWarning($"玩家【ID:{ID}】心跳超时,已被踢出服务器!");ServerEntry.Current.DestroyPlayer(this);break;}try{//1秒进行一次超时判断await Task.Delay(1000, token).ConfigureAwait(false);}catch (Exception){ }}}try{//单次心跳轮询结束,等待间隔时间后进行下一次await Task.Delay(_heartbeatInterval, token).ConfigureAwait(false);}catch (Exception) { }}}

需注意的是,Task.Delay方法的使用几乎都包裹在try-catch块内,这是因为其传入了可取消的token参数,当token在外部被取消时,Task.Delay方法会抛出一个TaskCanceledException异常,表明任务被取消,所以我们只需要捕获并抛弃他即可,否则程序会崩溃。

在这里插入图片描述

3.客户端响应心跳包

IsHeartbeatPass = true即代表了客户端在当前心跳轮询中响应了心跳包,也即是心跳检测通过。

在上一篇网络通信协议中,有提到将IsHeartbeatPass设置为true的时机,即在服务端接收到客户端的消息时:

        /// <summary>/// 从客户端接收数据/// </summary>/// <param name="token">用于取消异步的token</param>/// <returns>网络消息</returns>protected async Task<NetworkMessage?> ReceiveDataAsync(CancellationToken token){if (_socket == null || !IsConnect)return null;try{//接收消息校验码(4字节)await ReceiveFixedBytes(_socket, _receiveBuffer, _receiveCodeData, token).ConfigureAwait(false);int code = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(_receiveCodeData, 0));//通过消息校验码,判断当前接收到的是心跳包if (code == HEARTBEAT){//设置心跳检测通过IsHeartbeatPass = true;return null;}//......}}

然后回到客户端代码中,客户端在收到服务端的心跳包后,会立即做出响应:

namespace HT.Framework
{/// <summary>/// 默认的TCP协议通道/// </summary>public sealed class TcpChannel : ProtocolChannelBase{/// <summary>/// 接收消息/// </summary>/// <param name="client">客户端</param>/// <returns>接收到的消息对象</returns>protected override INetworkMessage ReceiveMessage(Socket client){try{//接收消息校验码(4字节)ReceiveFixedBytes(client, _receiveBuffer, _receiveCodeData);int code = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(_receiveCodeData, 0));//通过消息校验码,判断当前接收到的是心跳包if (code == HEARTBEAT){//响应心跳包//将心跳包注入到待发送消息队列InjectMessage(_heartbeatPackage);return null;}//......}}}
}

设置IsHeartbeatPass = true后,一次心跳检测轮询就算完成了,且心跳检测通过,服务端会认为该客户端处于正常连接中,等待进行下一次心跳轮询。

在这里插入图片描述

http://www.dtcms.com/a/478183.html

相关文章:

  • 二叉树的深搜
  • C++设计模式之行为型模式:模板方法模式(Template Method)
  • 做3dh春丽网站叫什么重庆十大软件公司
  • 长沙电商网站开发php开发网站后台
  • QT6中Combo Box与Combo BoxFont 功能及用法
  • 软考网工知识点-1
  • win10下Qt应用程序使用FastDDS
  • 链表相关的知识以及算法题
  • 模板网站建站步骤微信公众号和小程序的区别
  • Shell 使用指南
  • 重庆网站seo服务没效果
  • 开源项目重构我们应该怎么做-以 SQL 血缘系统开源项目为例
  • Sora2:AIGC的技术革命与生态重构
  • Modbus RTU 数据结构(发送和返回/读/写)
  • Nginx IP 透传
  • 海外IP的主要应用业务
  • 门户网站建设工序做微信网站要多少钱
  • 南阳网站优化费用推进网站 集约化建设
  • 算法训练之BFS实现FloodFill算法
  • Typescript - 枚举类型 enum,详细介绍与使用教程(快速入门)
  • 机器视觉2D贴合引导项目从哪里入手,案例知识分享
  • 家庭烹饪用油选择
  • 「工具设计」JS字段信息加密解密工具设计
  • 注意力机制-10.1.3注意力可视化
  • 网站维护公司苏州网站推广优化
  • Codeforces Educational 183(ABCD)
  • 为什么建设网站要年年交钱石家庄最新今天消息
  • 2025年语音识别(ASR)与语音合成(TTS)技术趋势分析对比
  • TortoiseSVN-1.8.10.26129-x64-svn-1.8.11.msi
  • 鸿蒙NEXT应用接入快捷栏:一键直达,提升用户体验