Unity 基于NTP服务器获取网络时间

内容将会持续更新,有错误的地方欢迎指正,谢谢!
 

Unity 基于NTP服务器获取网络时间
     
TechX 坚持将创新的科技带给世界!

拥有更好的学习体验 —— 不断努力,不断进步,不断探索
TechX —— 心探索、心进取!

助力快速实现 NTP 获取网络时间

为初学者节省宝贵的学习时间,避免困惑!


前言:

  当我们需要准确的时间信息时,依赖于本地系统时间可能会带来不准确的结果,特别是在需要确保时间的一致性和精确性的应用中。为了解决这个问题,我们可以通过网络时间协议(NTP)服务器来获取网络时间,从而获得更准确的时间信息。本篇博客将介绍如何在Unity中使用C#编写代码来从NTP服务器获取网络时间。


在这里插入图片描述



一、什么是NTP?


NTP,全称网络时间协议(Network Time Protocol),是一种用于同步计算机网络中设备的时间的协议。它允许计算机通过互联网或局域网获取精确的时间信息,以确保设备在分布式系统中保持时间的一致性。NTP协议的工作原理基于一组公共的时间服务器,这些服务器提供了准确的时间信息。


二、获取NTP服务器地址


要从NTP服务器获取时间,首先需要知道可用的NTP服务器的地址。以下是一些常用的NTP服务器地址,这些地址通常是可用的:
  1. pool.ntp.org
  2. cn.pool.ntp.org
  3. ntp1.aliyun.com
  4. ntp2.aliyun.com
  5. ntp3.aliyun.com
  6. asia.pool.ntp.org
  7. time.windows.com
  8. time1.cloud.tencent.com

也可以从http://www.ntp.org.cn/pool这个网址中查找自己想要的服务器地址。

在这里插入图片描述



三、通过NTP服务器获取网络时间


1、同步方式获取网络时间

/// <summary>
/// 获取网络时间 utc时间
/// </summary>
/// <param name="ntpServer"></param>
/// <param name="timeoutMilliseconds "></param>
/// <returns></returns>
private static DateTime GetNetworkTime(string ntpServer, int timeoutMilliseconds = 5000)
{
    try
    {
        const int udpPort = 123;

        var ntpData = new byte[48];                                                             // 创建一个 48 字节大小的字节数组来存储 NTP 数据
        ntpData[0] = 0x1B;                                                                      // 将 NTP 数据的第一个字节设置为 0x1B,这是 NTP 协议的请求数据格式

        var addresses = Dns.GetHostEntry(ntpServer).AddressList;                                // 获取 NTP 服务器的 IP 地址列表
        var ipEndPoint = new IPEndPoint(addresses[0], udpPort);                                 // 创建用于连接的 IP 端点,使用第一个 IP 地址和 NTP 服务器的端口 123
        var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);// 创建套接字,使用 IPv4 地址族、数据报套接字类型和 UDP 协议类型

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

        socket.Connect(ipEndPoint);                                                             // 连接到 NTP 服务器
        socket.Send(ntpData);                                                                   // 发送 NTP 数据
        socket.Receive(ntpData);                                                                // 接收 NTP 响应数据
        socket.Close();                                                                         // 关闭套接字连接

        const byte serverReplyTime = 40;                                                        // 服务器响应时间在 NTP 数据中的偏移量
        ulong intPart = BitConverter.ToUInt32(ntpData, serverReplyTime);                        // 从 NTP 数据中获取无符号 32 位整数部分
        ulong fractPart = BitConverter.ToUInt32(ntpData, serverReplyTime + 4);                  // 从 NTP 数据中获取无符号 32 位小数部分
                                                                                                // 交换整数部分和小数部分的字节顺序,以适应本地字节顺序
        intPart = SwapEndianness(intPart);
        fractPart = SwapEndianness(fractPart);

        var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);              // 将整数部分和小数部分转换为毫秒数
        var networkDateTime = (new DateTime(1900, 1, 1)).AddMilliseconds((long)milliseconds);   // 根据毫秒数计算网络时间(从 1900 年 1 月 1 日开始计算)

        TimeZoneInfo serverTimeZone = TimeZoneInfo.Local;                                       // 服务器的时区

        networkDateTime = TimeZoneInfo.ConvertTimeFromUtc(networkDateTime, serverTimeZone);

        return networkDateTime;
    }
    catch (Exception ex)
    {
        Debug.Log("获取网络时间失败: " + ex.Message);
        return DateTime.MinValue;
    }
}

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

2、异步方式获取网络时间

/// <summary>
/// 异步获取时间 utc时间
/// </summary>
/// <param name="ntpServer"></param>
/// <param name="timeoutMilliseconds"></param>
/// <returns></returns>
private async static Task<DateTime> GetNetworkTimeAsync(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 networkDateTime = new DateTime(1900, 1, 1).AddMilliseconds((long)milliseconds);

        TimeZoneInfo serverTimeZone = TimeZoneInfo.Local; // 服务器的时区

        networkDateTime = TimeZoneInfo.ConvertTimeFromUtc(networkDateTime, serverTimeZone);

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

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


四、通过NTP服务器池获取网络时间


使用多个NTP服务器池有助于确保你的应用程序获得准确、高可用性的时间信息,并降低了单点故障的风险。这是一个非常有用的时间管理策略,特别是对于需要时间同步的应用程序和系统。
/// <summary>
/// 同步获取网络时间(UTC时间)使用多个NTP服务器(池)
/// </summary>
/// <returns>获取到的网络时间</returns>
public static DateTime GetNetworkTimePool()
{
   var ntpServerAddresses = new List<string>
   {
       "cn.pool.ntp.org",
       "ntp1.aliyun.com" ,
       "ntp2.aliyun.com" ,
       "ntp3.aliyun.com" ,
       "ntp4.aliyun.com" ,
       "ntp5.aliyun.com" ,
       "ntp6.aliyun.com" ,
       "cn.pool.ntp.org" ,
       "asia.pool.ntp.org" ,
       "time.windows.com" ,
       "time1.cloud.tencent.com"
   };

   foreach (var serverAddress in ntpServerAddresses)
   {
       DateTime networkDateTime = GetNetworkTime(serverAddress, 2000);
       if (networkDateTime != DateTime.MinValue)
       {
           Debug.Log("获取网络时间:" + networkDateTime);

           return networkDateTime;
       }
   }

   Debug.Log("获取系统时间:" + DateTime.Now);
   return DateTime.Now;
}

/// <summary>
/// 异步获取网络时间(UTC时间)使用多个NTP服务器(池)
/// </summary>
/// <returns>获取到的网络时间</returns>
public static async Task<DateTime> GetNetworkTimeAsyncPool()
{
   var ntpServerAddresses = new List<string>
   {
       "cn.pool.ntp.org",
       "ntp1.aliyun.com" ,
       "ntp2.aliyun.com" ,
       "ntp3.aliyun.com" ,
       "ntp4.aliyun.com" ,
       "ntp5.aliyun.com" ,
       "ntp6.aliyun.com" ,
       "cn.pool.ntp.org" ,
       "asia.pool.ntp.org" ,
       "time.windows.com" ,
       "time1.cloud.tencent.com"
   };

   var tasks = ntpServerAddresses.Select(serverAddress => Task.Run(async () => await GetNetworkTimeAsync(serverAddress, 2000))).ToArray();

   while (tasks.Length > 0)
   {
       var completedTask = await Task.WhenAny(tasks);

       tasks = tasks.Where(task => task != completedTask).ToArray();

       DateTime networkDateTime = completedTask.Result;

       if (networkDateTime != DateTime.MinValue)
       {
           Debug.Log("获取网络时间:" + networkDateTime);

           return networkDateTime;
       }
   }

   Debug.Log("获取系统时间:" + DateTime.Now);
   return DateTime.Now;
}




TechX —— 心探索、心进取!

每一次跌倒都是一次成长

每一次努力都是一次进步

END
感谢您阅读本篇博客!希望这篇内容对您有所帮助。如果您有任何问题或意见,或者想要了解更多关于本主题的信息,欢迎在评论区留言与我交流。我会非常乐意与大家讨论和分享更多有趣的内容。
如果您喜欢本博客,请点赞和分享给更多的朋友,让更多人受益。同时,您也可以关注我的博客,以便及时获取最新的更新和文章。
在未来的写作中,我将继续努力,分享更多有趣、实用的内容。再次感谢大家的支持和鼓励,期待与您在下一篇博客再见!
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

沐沐森的故事

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

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

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

打赏作者

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

抵扣说明:

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

余额充值