什么是.NET应用程序最稳定的时间戳源?

Hei*_*tes 26 c# datetime

基础问题

我的C#应用​​程序中存在时间戳问题.我异步地从远程TCP连接接收数据.每次收到数据时,我都会将时间戳变量更新为DateTime.Now.在一个单独的线程上,每秒一次,我检查它是否超过自我上次收到后的预定义超时时间,如果是,则断开连接.这种方法已经工作了很多年,但现在我的情况是应用程序安装在具有不稳定时间源的机器上.每隔几天,机器时间"自动校正",我提前断开连接.代码基本如下:

接收流程

void OnReceiveComplete(IAsyncResult ar) {
    ...
    mySocket.EndReceive(ar);
    lastRxTime = DateTime.Now;
    ...
}
Run Code Online (Sandbox Code Playgroud)

检查流程

void CheckConnection() {
    TimeSpan ts = DateTime.Now.Subtract(lastRxTime);
    if(ts.TotalMilliseconds > timeout) {
        Disconnect(string.Format("No packet received from the server for over {0} seconds.", timeout / 1000));
    }
}
Run Code Online (Sandbox Code Playgroud)

在问题发生期间我有有效的Wireshark捕获,在断开连接之前我看到NTP流量最终看起来像是至少1分钟的修正.这显然会导致检查过程失败.

技术细节/预期问题的答案

  1. 我可以控制连接的两端,但不能控制它们之间的物理层(通常是低质量的卫星链路),这就是为什么要进行超时检查的原因.
  2. 由于数据是异步的,因此如果没有数据从服务器发送一段时间等于超时的一半,则可以发送一个小的心跳.
  3. 此过程可以有多个实例(即在连接到多个不同服务器的计算机上).
  4. 所有通信都使用异步方法(据称使用完成端口).
  5. 检查过程在单个线程上运行,该线程由单个计算机上的所有客户端共享.

完成研究(即可能的解决方案)

我的谷歌搜索到目前为止导致以下结果:

  1. 我意识到我应该使用DateTime.UtcNow而不是DateTime.Now出于性能原因.这不会影响问题本身.
  2. 依赖于Ticks的实现将是更好的解决方案.
  3. 获得滴答有两种选择 - Environment.TickCountStopwatch.GetTimestamp()
  4. 根据我的研究,Environment.TickCount可能会受到时间调整的影响,但我不确定在什么情况下.此外,由于我在其他更高性能的情况下使用相同的方法,10-16毫秒的分辨率可能是一个问题(虽然不是我在这里提出的具体情况).
  5. 当高性能时钟不可用时,Stopwatch.GetTimestamp()可以回退DateTime.Now.Ticks.我不确定这种情况会发生多久(任何机器都不再配备高性能时钟),但我确信如果它转向Ticks会出现同样的问题.
  6. 我还读过Stopwatch.GetTimestamp()将使用QueryPerformanceCounter()API调用,并且从多个线程调用时可能会不稳定.

终极问题

我很好奇生成lastRxTime时间戳的最佳方法是什么?我是否过分担心Environment.TickCountStopwatch.GetTimestamp()功能中的低可能性问题?只要他们将应用程序的多线程特性以及链路质量问题考虑在内,我就会对其他实现持开放态度.

更新7/17/2013(已部署解决方案!)

我已经部署了一个解决方案,并希望让每个人都了解详细信息.一般来说,可能没有一个可接受的答案,但在经历了这个经历之后,我可以说最初的解决方案肯定是一个问题.我会尽量提供尽可能详细的信息:

首先,NTP问题实际上是一个不同问题的症状.出现此问题的网络是AD Domain,其中两台服务器运行我的代码设置为域控制器.事实证明,DC是域的时间源.事实证明,系统时间从这些系统上的实时时钟漂移了大约11天的一分钟,此时Windows正在纠正滑动.一旦它校正了第一个DC上的滑差,第二个DC就会同步他的时间并且都会遇到上述问题.

根据反馈和我原来的研究,我创建了一个测试程序,用于在断开日期时间,Date.TickCount和Stopwatch.GetTimestamp()的日志值时运行.我发现在校正过程中,Environment.TickCount和StopWatch.GetTimeStamp()都没有滑落,这意味着它们很适合用作DateTime.Now()的替代品.我选择了TickCount,因为它保证在我所有已部署的服务器上(而秒表可以回退到我尚未找到的某些机器上的DateTime对象).它到目前为止没有问题.我在滚动问题上做了尽职尽责,以防止这种形式出现问题,但需要等待我的系统长时间确定.

我想要指出的是,如果其他人遇到类似的问题,他们不应该忽视下面列表中任何其他呈现的解决方案的使用.每个都有自己的优点,事实上简单的计数器可能是大多数情况下的最佳解决方案.我没有采用这种解决方案的原因是我在单独的区域中有类似的代码,严重依赖于紧密的时间.我可以处理那里的滴答计数的16毫秒左右的分辨率,但无法处理计数器解决方案产生的时间漂移​​(我在一个单独的产品中使用了这样的代码,这个产品每小时漂移超过一秒钟并带给我超出项目规范).

再次,感谢所有人,如果还有更多,我一定会更新问题.

Ree*_*sey 2

我是否过于担心 Environment.TickCount 和 Stopwatch.GetTimestamp() 函数中的低可能性问题?

有可能,是的。

我更喜欢这个Stopwatch.GetTimestamp()选择。如果您的系统上可用,这将使用高性能计时器;DateTime.Ticks如果这不是一个选项,则回退到该计时器。因此,在给定硬件上可用时,您将获得尽可能最佳的计时,并且在高性能计时器不可用时作为后备方案是一个不错的选择。

虽然Environment.TickCount这可能是一个有趣的选择,但如果您的计算机可能运行超过 24.9 天,您将需要处理溢出的情况。

  • 如果您的环境支持,对 [`GetTickCount64`](http://msdn.microsoft.com/en-us/library/windows/desktop/ms724411(v=vs.85).aspx) 的 pinvoke 调用是一个不错的选择替代Environment.TickCount,因为它不存在滚动问题。 (3认同)