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

news/2024/5/18 13:38:21 标签: 网络, 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

    }
}


http://www.niftyadmin.cn/n/5302708.html

相关文章

进程与线程的/并行与并发/同步与异步

1进程与线程的区别 1.1进程 进程是程序的一次执行过程&#xff0c;是系统运行程序的基本单位&#xff0c;因此进程是动态的。系统运行一个程序即是一个进程从创建&#xff0c;运行到消亡的过程。在 Java 中&#xff0c;当我们启动 main 函数时其实就是启动了一个 JVM 的进程&…

离线部署的MinIO

网络有不同的部分&#xff0c;例如 DMZ、公共、私有、堡垒等。这实际上取决于您的组织和网络要求。在部署应用程序时&#xff0c;任何应用程序&#xff0c;我们都需要考虑类型以及它是否需要位于网络的特定部分。 例如&#xff0c;如果要部署数据库&#xff0c;则不希望它位于…

【MATLAB第88期】基于MATLAB的6种神经网络(ANN、FFNN、CFNN、RNN、GRNN、PNN)多分类预测模型对比含交叉验证

【MATLAB第88期】基于MATLAB的6种神经网络&#xff08;ANN、FFNN、CFNN、RNN、GRNN、PNN&#xff09;多分类预测模型对比含交叉验证 前言 本文介绍六种类型的神经网络分类预测模型 1.模型选择 前馈神经网络 (FFNN) 人工神经网络 (ANN) 级联前向神经网络 (CFNN) 循环神经网…

如何理解int main(int argc, char** argv)的参数?

int main(int argc, char** argv) 是 C 和 C 程序的入口点&#xff0c;其中 argc 和 argv 是用来接收从命令行传递给程序的参数的。下面我将详细解释这两个参数的含义&#xff0c;并给出一个例子来帮助理解。 参数解释 int argc&#xff1a; argc 是 "argument count&quo…

odoo17 | 基本视图

前言 我们在上一章中已经看到Odoo能够为给定模型生成默认视图。在实践中&#xff0c;默认视图是绝对不可接受的用于商业应用程序。相反&#xff0c;我们至少应该以逻辑方式组织各种字段。 视图在带有动作和菜单的XML文件中定义。它们是ir.ui.view模型的实例。 在我们的房地产…

深信服技术认证“SCSA-S”划重点:文件包含漏洞

为帮助大家更加系统化地学习网络安全知识&#xff0c;以及更高效地通过深信服安全服务认证工程师考核&#xff0c;深信服特别推出“SCSA-S认证备考秘笈”共十期内容&#xff0c;“考试重点”内容框架&#xff0c;帮助大家快速get重点知识~ 划重点来啦 *点击图片放大展示 深信服…

mac电脑配置本地连接开发机器一键打包部署

mac电脑 安装homebrew&#xff08;已安装请跳过&#xff09; /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"安装rsync同步工具 brew install rsync配置服务器免密 生成公/私钥&#xff08;生成过的请跳过&a…

分析Java中的StringHelper类

目录 前言1. 概念2. 功能示例3. Demo示例 前言 在项目中实战学习并记录可用的工具类 1. 概念 Java标准库&#xff08;java.lang包&#xff09;并没有提供名为StringHelper的类。通常&#xff0c;类似的字符串处理工具类并不是Java标准库的一部分&#xff0c;而是由程序员自行…