将应用程序的时间与外部服务器的时间同步的最佳方法是什么?

时间:2013-02-28 08:06:57

标签: c# synchronization clock

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

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

4 个答案:

答案 0 :(得分:4)

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

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

这总是假设您希望它在您的应用程序中是本地的,当然。另一个替代方案(可能不合适,取决于您的上下文)是要求主机运行时间同步客户端(我相信 Windows默认情况下这些天)并且如果差异则只是开始失败服务器和客户端之间的关系太大了。 (无论如何,它永远不会完全同步,或者至少不会很长时间 - 你需要允许一些余地。)

答案 1 :(得分:2)

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

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

总结一下:

  • 至少检查服务器端请求的时间
  • 请勿更改客户端计算机的时间,但在应用程序中显示某种指示符

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

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

请记住,几乎不可能验证此客户端之类的请求。所以你必须在服务器端内置一些检查!

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

答案 2 :(得分:1)

好吧,因为它已经6岁了,但是不得不处理类似的网络游戏问题。

采用了一种我称为“ marco-polo”的技术,其原因很快就会显现出来。它要求两个时钟能够交换消息,而其准确性取决于它们可以做到的速度。

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

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

git@github.com:YourName/RepoName.git

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

// Log the timestamp
localTime_Marco_Send = DateTime.UtcNow;

// Send that to clock A
SendSyncRequest();

// Wait for an answer
Sleep(..);

然后回到时钟B ::

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

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

// 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;
}

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

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

答案 3 :(得分:0)

您的服务器应始终以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;
    }