前言:

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

一、什么是NTP?

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

二、获取NTP服务器地址

要从NTP服务器获取时间,首先需要知道可用的NTP服务器的地址。以下是一些常用的NTP服务器地址,这些地址通常是可用的:
pool.ntp.org
cn.pool.ntp.org
ntp1.aliyun.com
ntp2.aliyun.com
ntp3.aliyun.com
asia.pool.ntp.org
time.windows.com
time1.cloud.tencent.com
也可以从http://www.ntp.org.cn/pool这个网址中查找自己想要的服务器地址。

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

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

  1. /// <summary>
  2. /// 获取网络时间 utc时间
  3. /// </summary>
  4. /// <param name="ntpServer"></param>
  5. /// <param name="timeoutMilliseconds "></param>
  6. /// <returns></returns>
  7. private static DateTime GetNetworkTime(string ntpServer, int timeoutMilliseconds = 5000)
  8. {
  9. try
  10. {
  11. const int udpPort = 123;
  12. var ntpData = new byte[48]; // 创建一个 48 字节大小的字节数组来存储 NTP 数据
  13. ntpData[0] = 0x1B; // 将 NTP 数据的第一个字节设置为 0x1B,这是 NTP 协议的请求数据格式
  14. var addresses = Dns.GetHostEntry(ntpServer).AddressList; // 获取 NTP 服务器的 IP 地址列表
  15. var ipEndPoint = new IPEndPoint(addresses[0], udpPort); // 创建用于连接的 IP 端点,使用第一个 IP 地址和 NTP 服务器的端口 123
  16. var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);// 创建套接字,使用 IPv4 地址族、数据报套接字类型和 UDP 协议类型
  17. // 设置超时时间
  18. socket.ReceiveTimeout = timeoutMilliseconds;
  19. socket.Connect(ipEndPoint); // 连接到 NTP 服务器
  20. socket.Send(ntpData); // 发送 NTP 数据
  21. socket.Receive(ntpData); // 接收 NTP 响应数据
  22. socket.Close(); // 关闭套接字连接
  23. const byte serverReplyTime = 40; // 服务器响应时间在 NTP 数据中的偏移量
  24. ulong intPart = BitConverter.ToUInt32(ntpData, serverReplyTime); // 从 NTP 数据中获取无符号 32 位整数部分
  25. ulong fractPart = BitConverter.ToUInt32(ntpData, serverReplyTime + 4); // 从 NTP 数据中获取无符号 32 位小数部分
  26. // 交换整数部分和小数部分的字节顺序,以适应本地字节顺序
  27. intPart = SwapEndianness(intPart);
  28. fractPart = SwapEndianness(fractPart);
  29. var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L); // 将整数部分和小数部分转换为毫秒数
  30. var networkDateTime = (new DateTime(1900, 1, 1)).AddMilliseconds((long)milliseconds); // 根据毫秒数计算网络时间(从 1900 年 1 月 1 日开始计算)
  31. TimeZoneInfo serverTimeZone = TimeZoneInfo.Local; // 服务器的时区
  32. networkDateTime = TimeZoneInfo.ConvertTimeFromUtc(networkDateTime, serverTimeZone);
  33. return networkDateTime;
  34. }
  35. catch (Exception ex)
  36. {
  37. Debug.Log("获取网络时间失败: " + ex.Message);
  38. return DateTime.MinValue;
  39. }
  40. }
  41. // 交换字节顺序,将大端序转换为小端序或反之
  42. private static uint SwapEndianness(ulong x)
  43. {
  44. return (uint)(((x & 0x000000ff) << 24) +
  45. ((x & 0x0000ff00) << 8) +
  46. ((x & 0x00ff0000) >> 8) +
  47. ((x & 0xff000000) >> 24));
  48. }

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

  1. /// <summary>
  2. /// 异步获取时间 utc时间
  3. /// </summary>
  4. /// <param name="ntpServer"></param>
  5. /// <param name="timeoutMilliseconds"></param>
  6. /// <returns></returns>
  7. private async static Task<DateTime> GetNetworkTimeAsync(string ntpServer, int timeoutMilliseconds = 5000)
  8. {
  9. try
  10. {
  11. const int udpPort = 123;
  12. var ntpData = new byte[48];
  13. ntpData[0] = 0x1B;
  14. var addresses = await Dns.GetHostAddressesAsync(ntpServer);
  15. var ipEndPoint = new IPEndPoint(addresses[0], udpPort);
  16. var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  17. // 设置超时时间
  18. socket.ReceiveTimeout = timeoutMilliseconds;
  19. await socket.ConnectAsync(ipEndPoint);
  20. await socket.SendAsync(new ArraySegment<byte>(ntpData), SocketFlags.None);
  21. var receiveBuffer = new byte[48];
  22. await socket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), SocketFlags.None);
  23. socket.Dispose();
  24. const byte serverReplyTime = 40;
  25. ulong intPart = BitConverter.ToUInt32(receiveBuffer, serverReplyTime);
  26. ulong fractPart = BitConverter.ToUInt32(receiveBuffer, serverReplyTime + 4);
  27. intPart = SwapEndianness(intPart);
  28. fractPart = SwapEndianness(fractPart);
  29. var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);
  30. var networkDateTime = new DateTime(1900, 1, 1).AddMilliseconds((long)milliseconds);
  31. TimeZoneInfo serverTimeZone = TimeZoneInfo.Local; // 服务器的时区
  32. networkDateTime = TimeZoneInfo.ConvertTimeFromUtc(networkDateTime, serverTimeZone);
  33. return networkDateTime;
  34. }
  35. catch (Exception ex)
  36. {
  37. // 出现异常,返回 null 或抛出错误,视情况而定
  38. Debug.Log("获取网络时间失败: " + ex.Message);
  39. return DateTime.MinValue;
  40. }
  41. }
  42. // 交换字节顺序,将大端序转换为小端序或反之
  43. private static uint SwapEndianness(ulong x)
  44. {
  45. return (uint)(((x & 0x000000ff) << 24) +
  46. ((x & 0x0000ff00) << 8) +
  47. ((x & 0x00ff0000) >> 8) +
  48. ((x & 0xff000000) >> 24));
  49. }

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

使用多个NTP服务器池有助于确保你的应用程序获得准确、高可用性的时间信息,并降低了单点故障的风险。这是一个非常有用的时间管理策略,特别是对于需要时间同步的应用程序和系统。

  1. /// <summary>
  2. /// 同步获取网络时间(UTC时间)使用多个NTP服务器(池)
  3. /// </summary>
  4. /// <returns>获取到的网络时间</returns>
  5. public static DateTime GetNetworkTimePool()
  6. {
  7. var ntpServerAddresses = new List<string>
  8. {
  9. "cn.pool.ntp.org",
  10. "ntp1.aliyun.com" ,
  11. "ntp2.aliyun.com" ,
  12. "ntp3.aliyun.com" ,
  13. "ntp4.aliyun.com" ,
  14. "ntp5.aliyun.com" ,
  15. "ntp6.aliyun.com" ,
  16. "cn.pool.ntp.org" ,
  17. "asia.pool.ntp.org" ,
  18. "time.windows.com" ,
  19. "time1.cloud.tencent.com"
  20. };
  21. foreach (var serverAddress in ntpServerAddresses)
  22. {
  23. DateTime networkDateTime = GetNetworkTime(serverAddress, 2000);
  24. if (networkDateTime != DateTime.MinValue)
  25. {
  26. Debug.Log("获取网络时间:" + networkDateTime);
  27. return networkDateTime;
  28. }
  29. }
  30. Debug.Log("获取系统时间:" + DateTime.Now);
  31. return DateTime.Now;
  32. }
  33. /// <summary>
  34. /// 异步获取网络时间(UTC时间)使用多个NTP服务器(池)
  35. /// </summary>
  36. /// <returns>获取到的网络时间</returns>
  37. public static async Task<DateTime> GetNetworkTimeAsyncPool()
  38. {
  39. var ntpServerAddresses = new List<string>
  40. {
  41. "cn.pool.ntp.org",
  42. "ntp1.aliyun.com" ,
  43. "ntp2.aliyun.com" ,
  44. "ntp3.aliyun.com" ,
  45. "ntp4.aliyun.com" ,
  46. "ntp5.aliyun.com" ,
  47. "ntp6.aliyun.com" ,
  48. "cn.pool.ntp.org" ,
  49. "asia.pool.ntp.org" ,
  50. "time.windows.com" ,
  51. "time1.cloud.tencent.com"
  52. };
  53. var tasks = ntpServerAddresses.Select(serverAddress => Task.Run(async () => await GetNetworkTimeAsync(serverAddress, 2000))).ToArray();
  54. while (tasks.Length > 0)
  55. {
  56. var completedTask = await Task.WhenAny(tasks);
  57. tasks = tasks.Where(task => task != completedTask).ToArray();
  58. DateTime networkDateTime = completedTask.Result;
  59. if (networkDateTime != DateTime.MinValue)
  60. {
  61. Debug.Log("获取网络时间:" + networkDateTime);
  62. return networkDateTime;
  63. }
  64. }
  65. Debug.Log("获取系统时间:" + DateTime.Now);
  66. return DateTime.Now;
  67. }

最后可以根据上面自行选择进行同步还是异步的方式获取网络时间。
over,欢迎指正

来自: 【Unity】Unity 基于NTP服务器获取网络时间_unity里如何取网站的时间-CSDN博客