Unity 基于UDP实现本地时间与网络时间校验 防客户端修改日期作弊

新建一个Unity GameObject 挂上NTPComponent脚本

在这里插入图片描述

时间校验

在这里插入图片描述

源码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine.Networking;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading.Tasks;
using System.Linq;

namespace GameContent
{
    /// <summary>
    /// 启动游戏后,将所有地址列表遍历
    /// </summary>
    [DisallowMultipleComponent]
    public class NTPComponent : MonoBehaviour
    {
        [Range( 5f, 60f )]
        public float CheckDuration = 5f;

        [Header( "NTP服务器域名列表" )]
        public List<string> NTPServerAddressList = new List<string>
        {
            "cn.pool.ntp.org",//国际NTP快速授时服务
            "ntp.ntsc.ac.cn",
            "pool.ntp.org" ,//全球通用
            "time1.google.com" ,//谷歌
            "time2.google.com",
            "time3.google.com",
            "time4.google.com",
            "time.apple.com" ,//苹果
            "time1.apple.com",
            "time2.apple.com",
            "time3.apple.com",
            "time.windows.com" ,//微软
            "time.nist.gov" ,//美国
            "cn.ntp.org.cn",//中国
            "stdtime.gov.hk",//香港
            "ntp.tencent.com",//腾讯云
            "ntp.aliyun.com",//阿里云
        };

        /// <summary>
        /// 网络时间是否生效中
        /// </summary>
        public bool IsValid { get; private set; }
        /// <summary>
        /// 当前Utc时间
        /// </summary>
        public DateTime NowUtc { get; private set; }
        
        [ReadOnly] public bool IsSyncState = false;
        //[SerializeField]
        private float mResidualCheckTime = 0f;

        private Socket mSocket = null;

        private void Start( )
        {
            mResidualCheckTime = CheckDuration;
            IsValid = false;
            NowUtc = DateTime.UtcNow;

            SearchNTPAddresses( );
        }

        #region NTP服务
        private void Update( )
        {
            if ( IsValid )
                NowUtc.AddSeconds( Time.unscaledDeltaTime );

            //没间隔n秒就同步一次utc时间
            mResidualCheckTime -= Time.unscaledDeltaTime;
            if ( mResidualCheckTime <= 0 )
            {
                mResidualCheckTime = CheckDuration;
                SearchNTPAddresses( );
            }
        }

        public async void SearchNTPAddresses( )
        {
            var tasks = NTPServerAddressList.Select( serverAddress => Task.Run( async ( ) => await GetNetworkUtcTimeAsync( serverAddress, 2000 ) ) ).ToArray( );

            while ( tasks.Length > 0 )
            {
                var completedTask = await Task.WhenAny( tasks );
                DateTime networkDateTime = completedTask.Result;
                if ( networkDateTime != DateTime.MinValue )
                {
                    bool oldState = IsValid;

                    IsValid = true;
                    NowUtc = completedTask.Result;
                    //Debug.Log( $"<Color=#FF0000>NTP = {NowUtc}</Color>" );

                    TimeSpan diff = NowUtc - DateTime.UtcNow;
                    IsSyncState = Mathf.Abs( ( float ) diff.TotalSeconds ) <= 10;

                    if ( oldState == false )
                        Fire.Event( GameEvent.ConnectNTPEvent, this );
                    return;
                }
                else
                {
                    tasks = tasks.Where( task => task != completedTask ).ToArray( );
                }
            }

            IsValid = false;
            //GameEntry.Event.Fire(this, ConnectNTPEventArgs.Create(false));
        }

        /// <summary>
        /// 异步获取时间 utc时间
        /// </summary>
        private async Task<DateTime> GetNetworkUtcTimeAsync( string ntpServer, int timeoutMilliseconds = 5000 )
        {
            try
            {
                const int udpPort = 123;
                var ntpData = new byte[ 48 ];
                ntpData[ 0 ] = 0x1B;

                var addresses = await Dns.GetHostAddressesAsync( ntpServer );
                var ipEndPoint = new IPEndPoint( addresses[ 0 ], udpPort );
                var socket = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp );

                // 设置超时时间
                socket.ReceiveTimeout = timeoutMilliseconds;

                await socket.ConnectAsync( ipEndPoint );
                await socket.SendAsync( new ArraySegment<byte>( ntpData ), SocketFlags.None );
                var receiveBuffer = new byte[ 48 ];
                await socket.ReceiveAsync( new ArraySegment<byte>( receiveBuffer ), SocketFlags.None );
                socket.Dispose( );

                const byte serverReplyTime = 40;
                ulong intPart = BitConverter.ToUInt32( receiveBuffer, serverReplyTime );
                ulong fractPart = BitConverter.ToUInt32( receiveBuffer, serverReplyTime + 4 );
                intPart = SwapEndianness( intPart );
                fractPart = SwapEndianness( fractPart );
                var milliseconds = ( intPart * 1000 ) + ( ( fractPart * 1000 ) / 0x100000000L );
                var networkUtcDateTime = new DateTime( 1900, 1, 1 ).AddMilliseconds( ( long ) milliseconds );

                //TimeZoneInfo serverTimeZone = TimeZoneInfo.Local; // 服务器的时区
                //var networkDateTime = TimeZoneInfo.ConvertTimeFromUtc(networkUtcDateTime, serverTimeZone);

                return networkUtcDateTime;
            }
            catch ( Exception ex )
            {
                // 出现异常,返回 null 或抛出错误,视情况而定
                //Debug.Log("获取网络时间失败: " + ex.Message);
                return DateTime.MinValue;
            }
        }

        // 交换字节顺序,将大端序转换为小端序或反之
        private uint SwapEndianness( ulong x )
        {
            return ( uint ) ( ( ( x & 0x000000ff ) << 24 ) +
                           ( ( x & 0x0000ff00 ) << 8 ) +
                           ( ( x & 0x00ff0000 ) >> 8 ) +
                           ( ( x & 0xff000000 ) >> 24 ) );
        }
        #endregion

    }
}

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极客柒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值