使用Unitask定时调用请求,同步时间

    1. using Cysharp.Threading.Tasks;
    2. using GameContent;
    3. using System;
    4. using System.Collections.Generic;
    5. using System.Net;
    6. using System.Net.Sockets;
    7. using System.Threading;
    8. using UnityEngine;
    9. /// <summary>
    10. /// NTP服务器 网络时间
    11. /// 没隔10s请求一次网络时间,如果失败则弹出断网界面
    12. /// </summary>
    13. public static class NetworkUtcTimeTools
    14. {
    15. /// <summary>
    16. /// 获取计算后的本地UTC时间
    17. /// </summary>
    18. public static DateTime UtcNow
    19. {
    20. get
    21. {
    22. return mNetUtcTime.AddMilliseconds((Time.realtimeSinceStartupAsDouble - mRecordUnityStartupTime) * 1000);
    23. }
    24. }
    25. /// <summary>
    26. /// UI资源是否准备好了
    27. /// </summary>
    28. public static bool UIAssetIsOk = false;
    29. /// <summary>
    30. /// 是否成功获取到网络时间
    31. /// 在首次登录、从后台切回前台时,会重新获取网络时间
    32. /// </summary>
    33. public static bool IsSuccess { get; private set; } = false;
    34. /// <summary>
    35. /// 获取当前网络UTC时间
    36. /// </summary>
    37. private static DateTime mNetUtcTime = DateTime.MinValue;
    38. /// <summary>
    39. /// 当返回网络时间时,记录Unity的游戏开始以来的实时时间
    40. /// </summary>
    41. private static double mRecordUnityStartupTime = 0;
    42. /// <summary>
    43. /// 服务器地址列表
    44. /// </summary>
    45. private static List<string> mCachedNtpServerUrlList = new List<string>()
    46. {
    47. "time.cloudflare.com",
    48. "time.google.com",
    49. "pool.ntp.org",
    50. "time.aws.com",
    51. "time.windows.com",
    52. "time.apple.com",
    53. "time.nist.gov",
    54. "clock.isc.org",
    55. "ntp.ubuntu.com",
    56. };
    57. /// <summary>
    58. /// 是否显示断网提醒
    59. /// </summary>
    60. private static bool mIsShowWarn = false;
    61. /// <summary>
    62. /// 初始化网络时间
    63. /// </summary>
    64. [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
    65. private static void InitNetworkUtcTime()
    66. {
    67. Debug.Log("[网络时间] 开始初始化");
    68. IsSuccess = false;
    69. CyclicUpdateTime().Forget();
    70. }
    71. /// <summary>
    72. /// 循环同步时间
    73. /// </summary>
    74. /// <returns></returns>
    75. private static async UniTaskVoid CyclicUpdateTime()
    76. {
    77. while (true)
    78. {
    79. AsyncUpdateTime().Forget();
    80. await UniTask.Delay(10000, ignoreTimeScale: true);
    81. }
    82. }
    83. /// <summary>
    84. /// 异步同步时间
    85. /// </summary>
    86. private static async UniTaskVoid AsyncUpdateTime()
    87. {
    88. var cts = new CancellationTokenSource();
    89. cts.CancelAfter(5000);//5s后自动取消
    90. int urlCount = mCachedNtpServerUrlList.Count;
    91. UniTask<bool>[] taskArray = new UniTask<bool>[urlCount];
    92. bool[] taskCompletedArray = new bool[urlCount];
    93. for (int i = 0; i < urlCount; i++)
    94. {
    95. string url = mCachedNtpServerUrlList[i];
    96. taskArray[i] = UpdateTimeFromNtpServerAsync(i, url, cts);
    97. taskCompletedArray[i] = false;
    98. }
    99. await UniTask.DelayFrame(10, cancellationToken: cts.Token);
    100. int failureCount = 0;
    101. while (true)
    102. {
    103. failureCount = 0;
    104. for (int i = taskArray.Length - 1; i >= 0; i--)
    105. {
    106. if (taskCompletedArray[i])
    107. continue;
    108. var task = taskArray[i];
    109. if (task.Status == UniTaskStatus.Pending)
    110. continue;
    111. taskCompletedArray[i] = true;
    112. if (task.Status == UniTaskStatus.Succeeded)
    113. {
    114. if (task.GetAwaiter().GetResult())
    115. {
    116. CloseWarn();
    117. cts.Cancel();
    118. cts.Dispose();
    119. return;
    120. }
    121. }
    122. else
    123. {
    124. //Faulted or Canceled
    125. if (++failureCount >= mCachedNtpServerUrlList.Count)
    126. {
    127. //判定是否全部失败了
    128. ShowWarn();
    129. cts.Cancel();
    130. cts.Dispose();
    131. return;
    132. }
    133. }
    134. }
    135. if (cts.IsCancellationRequested)
    136. {
    137. //时间到了,但没有结果
    138. ShowWarn();
    139. cts.Cancel();
    140. cts.Dispose();
    141. return;
    142. }
    143. await UniTask.DelayFrame(10);
    144. }
    145. }
    146. private static void ShowWarn()
    147. {
    148. mIsShowWarn = true;
    149. if (SplashCanvasCtrl.Instance != null && SplashCanvasCtrl.Instance.IsActived)
    150. {
    151. //TODO:Loading上的断网提醒
    152. }
    153. if (UIAssetIsOk)
    154. NetworkWarnForm.ShowWarn();
    155. }
    156. private static void CloseWarn()
    157. {
    158. mIsShowWarn = false;
    159. if (SplashCanvasCtrl.Instance != null && SplashCanvasCtrl.Instance.IsActived)
    160. {
    161. }
    162. if (UIAssetIsOk)
    163. NetworkWarnForm.CloseWarn();
    164. }
    165. private static DateTime m_epochTime = new(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc);
    166. /// <summary>
    167. /// 更新网络时间
    168. /// </summary>
    169. private static async UniTask<bool> UpdateTimeFromNtpServerAsync(int checkIndex, string ntpServerDnsAddress, CancellationTokenSource ctsToken)
    170. {
    171. //ctsToken.Token.ThrowIfCancellationRequested();
    172. await UniTask.Delay(checkIndex * 100, cancellationToken: ctsToken.Token); //间隔下防止同时返回
    173. try
    174. {
    175. const int udpPort = 123;
    176. var ntpData = new byte[48];
    177. ntpData[0] = 0x1B;
    178. var addresses = await Dns.GetHostAddressesAsync(ntpServerDnsAddress);
    179. var ipEndPoint = new IPEndPoint(addresses[0], udpPort);
    180. using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
    181. socket.SendTimeout = 1000;
    182. socket.ReceiveTimeout = 1000;
    183. await socket.ConnectAsync(ipEndPoint);
    184. if (ctsToken.IsCancellationRequested)
    185. {
    186. //已成功更新,取消此任务。
    187. socket.Dispose();
    188. return true;
    189. }
    190. await socket.SendAsync(new ReadOnlyMemory<byte>(ntpData), SocketFlags.None, ctsToken.Token);
    191. //await socket.SendAsync(new ArraySegment<byte>(ntpData), SocketFlags.None);
    192. if (ctsToken.IsCancellationRequested)
    193. {
    194. //已成功更新,取消此任务。
    195. socket.Dispose();
    196. return true;
    197. }
    198. var receiveBuffer = new byte[48];
    199. await socket.ReceiveAsync(new Memory<byte>(receiveBuffer), SocketFlags.None, ctsToken.Token);
    200. //await socket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), SocketFlags.None);
    201. socket.Dispose();
    202. const byte serverReplyTime = 40;
    203. ulong intPart = BitConverter.ToUInt32(receiveBuffer, serverReplyTime);
    204. ulong fractPart = BitConverter.ToUInt32(receiveBuffer, serverReplyTime + 4);
    205. intPart = SwapEndianness(intPart);
    206. fractPart = SwapEndianness(fractPart);
    207. var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);
    208. var networkDateTime = m_epochTime.AddMilliseconds((long)milliseconds);
    209. //同步时间
    210. IsSuccess = true;
    211. mNetUtcTime = networkDateTime;
    212. mRecordUnityStartupTime = Time.realtimeSinceStartupAsDouble;
    213. Debug.Log($"[网络时间] checkIndex={checkIndex} 同步时间 {mNetUtcTime} ; {mRecordUnityStartupTime}");
    214. return true;
    215. }
    216. catch (Exception exception)
    217. {
    218. Debug.LogWarning($"[网络时间] {exception}");
    219. return false;
    220. }
    221. }
    222. // 交换字节顺序,将大端序转换为小端序或反之
    223. private static uint SwapEndianness(ulong x)
    224. {
    225. return (uint)(((x & 0x000000ff) << 24) + ((x & 0x0000ff00) << 8) + ((x & 0x00ff0000) >> 8) + ((x & 0xff000000) >> 24));
    226. }
    227. }