UnityC#请求网络时间

一些单机游戏想要做一些伪联网的功能,例如每日登录奖励等,由于直接使用本地时间DateTime.UtcNow,就要用到请求网络时间,以下是请求网络时间的几种方法。

1.从一些主流网站发送http请求获取

原理是通过对主流网站发送http请求,从返回的头获取Date,代码如下

UnityWebRequest request = UnityWebRequest.Get("https://www.microsoft.com");
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
    try
    {
        string todayDate = request.GetResponseHeader("date");
        if (IsDebug)
            Debug.Log($"GetNetworkTime todayDate {todayDate}");
    }
}

2.类似的,如果有cdn,可以向cdn发送http请求获取

原理和上面的类似,但是要注意请求cdnUrl时,要记得后面带?t={timestamp}防止cdn缓存获取到了上次请求的时间。

UnityWebRequest request = UnityWebRequest.Head(cdnUrl);
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
    try
    {
        string todayDate = request.GetResponseHeader("date");
        if (IsDebug)
            Debug.Log($"GetNetworkTime todayDate {todayDate}");
    }
}

3.最终版本

  using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Globalization;
  using UnityEngine;
  using UnityEngine.Networking;

  public static class NTPManager
  {
      private const long Epoch = 621355968000000000L;
      public static readonly DateTime UtcStartTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

      private static long FrameStartUpTime;
      /// <summary>
      /// 当前时间 毫秒
      /// </summary>
      public static long TimeNow => GetNow();
      public static DateTime NowDate => GetDateTime();
      private static long CorrectTime;
      private static System.Diagnostics.Stopwatch Watch;
      private static TimeZoneInfo mTimeZoneInfo;
      private static int TimeHour = 8;
      public static TimeZoneInfo TimeZoneInfo
      {
          get
          {
              if (mTimeZoneInfo == null)
              {
                  mTimeZoneInfo = SetGameTimeZone(TimeHour);
              }
              return mTimeZoneInfo;
          }
      }
      private const string GAME_TIME_ZONE = "Custom Game Standard Time";
      public static string CDN_TIME_URL;
      public static string[] primaryUrls = new[]
      {
         "https://www.microsoft.com",
         "https://www.google.com",
         "https://www.baidu.com",
      };

      /// <summary>
      /// 设置游戏指定时区
      /// </summary>
      /// <param name="timeZoneSpan"></param>
      public static TimeZoneInfo SetGameTimeZone(int hour)
      {
          TimeSpan timeSpan = new TimeSpan(hour, 0, 0);
          string sign = timeSpan.TotalSeconds < 0 ? "-" : "+";
          string offsetStr = $"{sign}{timeSpan.Hours:D2}:{timeSpan.Minutes:D2}";
          var GameTimeZone = TimeZoneInfo.CreateCustomTimeZone(offsetStr, timeSpan, GAME_TIME_ZONE, GAME_TIME_ZONE);
          return GameTimeZone;
      }
      public static long NextDayTime { get; private set; }
      public static void SetServerNow(DateTime dateTime)
      {
          Watch = System.Diagnostics.Stopwatch.StartNew();
          CorrectTime = (dateTime.Ticks - Epoch) / 10000;
          if (TimeNow > NextDayTime && NextDayTime != 0)
          {
              //GameData.GetInstance().CrossDay();
              CrossDay?.Invoke();
              NextDayTime = GetMillisecondsUntilNextDay();
              //XGameEventManager.Instance.Notify(XEventId.NOTIFY_DAY_CHANGE_MSG);
              if (IsDebug)
                  Debug.Log($"变天 [{ConvertFromTimestamp(TimeNow)}] NextDayTime {ConvertFromTimestamp(NextDayTime)} ");
          }
          else
          {
              NextDayTime = GetMillisecondsUntilNextDay();
          }
          if (IsDebug)
          {
              int offset = ((NextDayTime - TimeNow) / 1000).TimeLong2Int();
              Debug.Log($"CurTime [{ConvertFromTimestamp(TimeNow)}]  GetDateTime() [{GetDateTime()}] NextDayTime {ConvertFromTimestamp(NextDayTime)} 距离offset [{TimeUtil.FormatSecondDHM4(offset)}]");
          }
      }


      public static DateTime ConvertFromTimestamp(long timestamp)
      {
          // 将传入的时间戳转换为DateTime对象
          DateTime utcDateTime = UtcStartTime.AddMilliseconds(timestamp);
          // 将UTC时间转换为指定时区的时间
          DateTime localDateTime = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, TimeZoneInfo);
          return localDateTime;
      }
      /// <summary>
      /// 打印指定毫秒数的日期时间为 "yyyy-MM-dd HH:mm:ss:fff" 格式。
      /// </summary>
      /// <param name="nowTimems">当前时间的毫秒数 (从1970年1月1日开始计算)。</param>
      /// <returns>格式化的日期时间字符串。</returns>
      public static string PrintLocalTime(this long nowTimems)
      {
          DateTime dateTime = UtcStartTime.AddMilliseconds(nowTimems); // Convert milliseconds to ticks for DateTime
          string formattedDateTime = dateTime.ToString("yyyy-MM-dd HH:mm:ss:fff");
          return formattedDateTime;
      }

      /// <summary>
      /// 返回当前时区第二天零点的毫秒值。
      /// </summary>
      /// <returns>当前时区第二天零点的毫秒值。</returns>
      public static long GetMillisecondsUntilNextDay()
      {
          DateTime currentDate = GetDateTime();
          DateTime nextDay = currentDate.AddDays(1).Date; // Get the next day's midnight
          long millisecondsUntilNextDay = nextDay.ToTimestamp() * 1000;
          return millisecondsUntilNextDay;
      }

      /// <summary>
      /// 转时间戳
      /// </summary>
      /// <param name="dt"></param>
      /// <returns></returns>
      public static long ToTimestamp(this DateTime dt)
      {
          if (dt.Kind == DateTimeKind.Utc)
          {
              return (long)(dt - UtcStartTime).TotalSeconds;
          }
          else if (dt.Kind == DateTimeKind.Local)
          {
              return (long)(dt.ToUniversalTime() - UtcStartTime).TotalSeconds;
          }
          else
          {
              return (long)(TimeZoneInfo.ConvertTimeToUtc(dt, TimeZoneInfo) - UtcStartTime).TotalSeconds;
          }
      }

      public static float GetFrameStartUpTime()
      {
          return (float)((double)FrameStartUpTime / TimeSpan.TicksPerSecond);
      }
      public static Action CrossDay;
      public static void FixedUpdate()
      {
          if (Watch == null)
          {
              return;
          }
          FrameStartUpTime = Watch.ElapsedTicks;
          if (TimeNow > NextDayTime && NextDayTime != 0)
          {
              //GameData.GetInstance().CrossDay();
              CrossDay?.Invoke();
              NextDayTime = GetMillisecondsUntilNextDay();
              if (IsDebug)
                  Debug.Log($"变天 [{ConvertFromTimestamp(TimeNow)}] NextDayTime {ConvertFromTimestamp(NextDayTime)} ");
          }
      }

      public static bool IsSameDate(long time)
      {
          // 将传入的时间戳转换为DateTime对象
          DateTime utcDateTime = UtcStartTime.AddMilliseconds(time);
          // 将UTC时间转换为指定时区的时间
          DateTime localDateTime = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, TimeZoneInfo);
          DateTime now = GetDateTime();
          return localDateTime.Date.Equals(now.Date);
      }

      public static DateTime GetDateTimeWithMs(long time)
      {
          // 将传入的时间戳转换为DateTime对象
          DateTime utcDateTime = UtcStartTime.AddMilliseconds(time);
          // 将UTC时间转换为指定时区的时间
          DateTime localDateTime = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, TimeZoneInfo);
          return localDateTime;
      }
      public static long GetTimestampFromChinaTime(DateTime chinaTime)
      {
          // 明确指定这个时间是东八区时间
          DateTimeOffset chinaTimeOffset = new DateTimeOffset(chinaTime, TimeSpan.FromHours(TimeHour));
          return chinaTimeOffset.ToUnixTimeSeconds();
      }
      public static DateTime GetDateTime()
      {
          // 将传入的时间戳转换为DateTime对象
          DateTime utcDateTime = UtcStartTime.AddMilliseconds(GetNow());
          // 将UTC时间转换为指定时区的时间
          DateTime localDateTime = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, TimeZoneInfo);
          return localDateTime;
      }

      public static long GetNow()
      {
          return (CorrectTime + FrameStartUpTime / 10000);
      }


      public static IEnumerator GetNetworkTimeCoroutine(Action<bool, DateTime> action)
      {
          if (IsDebug)
              Debug.Log($"GetNetworkTimeCoroutine {UnityEngine.Application.internetReachability}");

          if (UnityEngine.Application.internetReachability == NetworkReachability.NotReachable)
          {
              action.Invoke(false, DateTime.Now);
              yield break;
          }
          yield return GetNetworkTime(action);
      }

      public static IEnumerator GetNetworkTime(Action<bool, DateTime> action)
      {
          string cdnUrl = CDN_TIME_URL; // 你的 CDN 时间服务器
          bool success = false;
          DateTime resultTime = DateTime.UtcNow;
          // 1. 请求 CDN(带重试)
          yield return GetNetworkTimeWithRetry(cdnUrl, 2, 0.2f, (cdnSuccess, cdnTime) =>
          {
              if (cdnSuccess)
              {
                  success = true;
                  resultTime = cdnTime;
              }
          });
          if (!success)
          {
              // 2. 并行请求主流服务器(微软、Google、Apple)
              yield return GetNetworkTimeFast(primaryUrls, (fastSuccess, fastTime) =>
              {
                  if (fastSuccess)
                  {
                      success = true;
                      resultTime = fastTime;
                  }
              });
          }

          // 3. 返回最终结果
          action?.Invoke(success, success ? resultTime : DateTime.UtcNow);
      }

      // 并行请求多个服务器(只要有一个成功就返回)
      private static IEnumerator GetNetworkTimeFast(string[] urls, Action<bool, DateTime> callback)
      {
          bool success = false;
          DateTime resultTime = DateTime.UtcNow;
          int completedRequests = 0;
          List<UnityWebRequest> requests = new List<UnityWebRequest>();

          // 启动所有请求
          foreach (string url in urls)
          {
              UnityWebRequest request = UnityWebRequest.Head(url);
              requests.Add(request);
              request.SendWebRequest();
          }

          // 等待所有请求完成(或有一个成功)
          while (!success && completedRequests < requests.Count)
          {
              for (int i = 0; i < requests.Count; i++)
              {
                  UnityWebRequest request = requests[i];
                  if (request == null) continue;

                  if (request.isDone)
                  {
                      completedRequests++;

                      if (request.result == UnityWebRequest.Result.Success)
                      {
                          string dateHeader = request.GetResponseHeader("Date");
                          if (!string.IsNullOrEmpty(dateHeader))
                          {
                              try
                              {
                                  resultTime = DateTime.ParseExact(
                                      dateHeader,
                                      "ddd, dd MMM yyyy HH:mm:ss 'GMT'",
                                      CultureInfo.InvariantCulture.DateTimeFormat,
                                      DateTimeStyles.AssumeUniversal).ToUniversalTime();

                                  success = true;
                                  break;
                              }
                              catch (Exception e)
                              {
                                  Debug.LogError($"Parse Date header error (url: {urls[i]}): {e}");
                              }
                          }
                      }
                      else
                      {
                          Debug.LogError($"Request failed (url: {urls[i]}): {request.error}");
                      }

                      request.Dispose();
                      requests[i] = null;
                  }
              }

              yield return null;
          }

          // 取消未完成的请求
          foreach (var request in requests)
          {
              if (request != null && !request.isDone)
              {
                  request.Abort();
                  request.Dispose();
              }
          }

          callback?.Invoke(success, resultTime);
      }

      // CDN 请求(带重试)
      private static IEnumerator GetNetworkTimeWithRetry(string url, int maxAttempts, float retryDelay, Action<bool, DateTime> callback)
      {
          for (int attempt = 1; attempt <= maxAttempts; attempt++)
          {
              using (UnityWebRequest request = UnityWebRequest.Head(url))
              {
                  yield return request.SendWebRequest();

                  if (request.result == UnityWebRequest.Result.Success)
                  {
                      string dateHeader = request.GetResponseHeader("Date");
                      if (!string.IsNullOrEmpty(dateHeader))
                      {
                          try
                          {
                              DateTime cdnTime = DateTime.ParseExact(
                                  dateHeader,
                                  "ddd, dd MMM yyyy HH:mm:ss 'GMT'",
                                  CultureInfo.InvariantCulture.DateTimeFormat,
                                  DateTimeStyles.AssumeUniversal);

                              callback?.Invoke(true, cdnTime.ToUniversalTime());
                              yield break;
                          }
                          catch (Exception e)
                          {
                              Debug.LogError($"Parse CDN Date header error (attempt {attempt}): {e}");
                          }
                      }
                  }
                  else
                  {
                      Debug.LogError($"Get CDN Time attempt {attempt} failed: {request.error}");
                  }
              }

              if (attempt < maxAttempts)
                  yield return new WaitForSeconds(retryDelay);
          }

          callback?.Invoke(false, DateTime.UtcNow);
      }
  }

其中primaryUrls 为自定义主流网站,SetGameTimeZone 可以设置时区,通过调用GetNetworkTimeCoroutine 时,返回是否成功获取到时间和时间,成功时调用设置时间SetServerNow方法,然后每固定帧调用FixedUpdate,然后TimeNow就可以返回真实的网络时间(毫秒);并且注册委托CrossDay可以实现跨天逻辑。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇