【问题标题】:What would be the best way to synchronize my application's time with outside server's time?将我的应用程序时间与外部服务器时间同步的最佳方法是什么?
【发布时间】:2013-02-14 08:15:49
【问题描述】:

我正在考虑将系统的本地时间更改为服务器的时间,然后使用它,但我敢打赌还有其他方法可以做到这一点。我一直试图在 c# 中找到类似时钟的东西,但找不到任何东西。我正在接收 DateTime 格式的服务器时间。

编辑: 我需要在服务器工作的同时使用我的应用程序。我只想获得一次服务器的时间,然后使用从服务器获得的时间让我的应用程序在一个while循环中工作。我的系统时间和服务器时间之间可能存在差异(甚至 5 秒),这就是我想要这样做的原因。

【问题讨论】:

  • 您实际上是在谈论时钟同步还是时区?如果您能提供更多详细信息会有所帮助。
  • 已编辑:P,抱歉问题不清楚
  • 请注意,更改本地系统时间不仅操作不友好,而且在大多数版本的 Windows 上都需要管理员权限。
  • 我添加了一个代码供您更改当地时间。您可以在客户端的配置中保存时区 ID,也可以将其从服务器发送到客户端
  • @Patryk:您的问题仍然不清楚。您是否担心服务器和本地应用程序位于不同的时区?如果是这样,请在两个地方都使用 UTC。或者您是否担心本地系统时钟会因为其他原因(例如慢 5 分钟)而显示与服务器时钟不同的内容?

标签: c# synchronization clock


【解决方案1】:

您的意思并不完全清楚,但您当然可以创建自己的 IClock 接口,您可以在代码中的任何地方使用该接口,然后编写一个与您的服务器(或与 NTP)定期同步的实现.

我的Noda Time 项目已经使用了可注入时钟的概念——不是为了同步,而是为了可测试性。 (时间服务基本上是一个依赖项。)基本上这个想法是可行的 :) 你可能找不到任何已经这样做的东西,但写起来应该不会太难。不过,您需要考虑如何调整时间 - 例如,如果服务器时间超过您的“上次服务器时间 + 本地时间测量值”,您可能希望逐渐调整它而不是离散跳跃。

这总是假设您确实希望它在您的应用程序中是本地的,当然。另一种选择(这可能不合适,具体取决于您的上下文)是要求主机运行时间同步客户端(我相信这些天 Windows 默认情况下会这样做),如果差异则简单地开始失败您的服务器和客户端之间变得太大。 (无论如何,它永远不会完全同步,或者至少不会很长时间 - 你需要留出一些余地。)

【讨论】:

  • @jonskeet ,我看过 Noda Time 项目,如果我可以谦虚地推荐一个扩展:我在 Java 中做过类似的事情(虽然没有那么令人印象深刻),它也帮助我们使用在注入时间上工作的注入定时器和执行器。因此,我们实时使用了真正的计时器和执行器,但出于测试目的,我们能够在单个线程上运行所有计时器和执行器,同时控制它们的前进。
  • @omerschleifer:恐怕这还不足以理解您的意思。我们确实提供了一个 FakeClock 实现——但 Noda Time 并不是一个线程库。拥有一个与 Noda Time 配合良好的线程库将是一个合理的 side-project,但我认为它不应该成为 Noda Time 本身的一部分。
【解决方案2】:

@JonSkeet 提供的同步时代的答案看起来不错,我只是想指出一些事情。

正如@Alexei 已经说过的,用户需要管理员权限才能更改他们的本地时间(至少在 Windows 中),但也可能存在其他可能导致时间不同步的问题(糟糕的互联网连接,黑客等)。这意味着不能保证客户端时间确实与服务器时间相同,因此您至少需要检查服务器端收到请求的时间。另外,这里可能还存在可用性问题,我是否希望应用程序能够更改我自己的本地计算机的时间?见鬼。

总结一下:

  • 至少检查请求服务器端的时间
  • 不要更改客户端机器的时间,而是在您的应用程序中显示某种指示器

如何处理应用程序中的指标可以通过多种方式完成。

  • 在您的应用程序(您最初的想法)中显示一个与服务器定期同步的时钟
  • 显示某种倒计时(“您可以在 x 秒后提交..”),在收到请求时向客户端推送 resetCountdown 请求。
  • 启用“发送按钮”或您拥有的任何功能,这类似于倒计时。

请记住,要验证客户端这样的请求几乎是不可能的。所以你必须在服务器端建立一些检查!

其实我是想写评论的,但是有点长.. :)

【讨论】:

    【解决方案3】:

    好吧,有点死灵法,因为这是 6 岁,但必须处理网络游戏的类似问题。

    采用了一种我称之为“马可波罗”的技术,原因很快就会很明显。它需要两个时钟能够交换消息,其准确性取决于它们能够以多快的速度进行交换。

    免责声明:我相当肯定我不是第一个这样做的人,而且这是同步两个时钟的最基本方法。我仍然没有找到这样做的记录方法。

    在时钟 B(我们尝试同步的时钟),我们执行以下操作 ::

    // Log the timestamp
    localTime_Marco_Send = DateTime.UtcNow;
    
    // Send that to clock A
    SendSyncRequest();
    
    // Wait for an answer
    Sleep(..);
    

    在时钟 A(参考时钟),我们有以下处理程序 ::

    // This is triggered by SendSyncRequest
    OnReceiveSyncRequest()
    {
        // We received "Marco" - Send "Polo"
        SendSyncReply(DateTime.UtcNow);
    }
    

    然后回到时钟 B ::

    // This is triggered by SendSyncReply
    OnReceiveSyncReply(DateTime remoteHalfTime)
    {
        // Log the time we received it
        DateTime localTime_Polo_Receive = DateTime.UtcNow;
    
        // The remote time is somewhere between the two local times
        // On average, it will be in the middle of the two
        DateTime localHalfTime = localTime_Marco_Send  + 
             (localTime_Polo_Receive - localTime_Marco_Send) / 2;
    
        // As a result, the estimated dT from A to B is
        TimeSpan estimatedDT_A_B = localHalfTime - remoteHalfTime;
    }
    

    因此,我们现在可以访问一个漂亮的 TimeSpan,我们可以从当前本地时间中减去来估算远程时间

    DateTime estimatedRemoteTime = DateTime.UtcNow - estimatedDT_A_B;
    

    此估算的准确性取决于发送-接收-发送-接收的往返时间,您还应考虑时钟漂移(您应该多次这样做):

    1. 往返时间。如果它是即时的,那么您将获得准确的 dT。如果来回需要1秒钟,你不知道延迟是在发送还是接收。结果,您的错误为 0
    2. 时钟漂移。 CPU 时钟漂移,可能每天 1 秒。因此,一旦潜在的漂移可能发挥重要作用,请再次进行投票。

    【讨论】:

      【解决方案4】:

      您的服务器应始终以 UTC 模式保存时间。
      您可以在服务器中像这样在 UTC 中节省时间:

        DateTime utcTime = new DateTime(0, DateTimeKind.Utc);
      

      或:

          DateTime utcTimeNow = DateTime.UtcNow;
      

      在客户端中,当您获得存储在 utc 中的时间时,您可以将其转换为本地时间,如下所示:

          public DateTime ToLocalTime(DateTime utcTime)
          {
              //Assumes that even if utcTime kind is no properly deifned it is indeed UTC time
              DateTime serverTime= new DateTime(utcTime.Ticks, DateTimeKind.Utc);
              return TimeZoneInfo.ConvertTimeFromUtc(serverTime, m_localTimeZone);            
          }
      

      如果你想改变你的本地时区,这里有一个关于如何从配置中读取时区的代码示例:

      string localTimeZoneId = sysParamsHelper.ReadString(LOCAL_TIME_ZONE_ID_KEY, LOCAL_TIME_ZONE_DEFAULT_ID);
          ReadOnlyCollection<TimeZoneInfo> timeZones = TimeZoneInfo.GetSystemTimeZones();
      
          foreach (TimeZoneInfo timeZoneInfo in timeZones)
          {                
             if(timeZoneInfo.Id.Equals(localTimeZoneId))
             {
                 m_localTimeZone = timeZoneInfo;
                 break;
             }
          }
      
          if (m_localTimeZone == null)
          {
              m_logger.Error(LogTopicEnum.AMR, "Could not find time zone with id: " + localTimeZoneId + " . will use default time zone (UTC).");
              m_localTimeZone = TimeZoneInfo.Utc;
          }          
      

      【讨论】:

      • 我不认为 OP真的在谈论时区,但我已经要求澄清......
      • 好的,如果我现在明白了,他想要的是更改本地时区的代码吗?我在答案中添加了一个代码示例。
      • 即使是 5 秒的差异,我也想避免。我不确定我的问题还能如何澄清
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-22
      • 2010-09-11
      • 2023-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多