应该使用 NTP 时间服务器的什么时间来设置我的时钟?

问题描述

我使用 python3 和 ntplib https://pypi.org/project/ntplib/查询 NTP 时间服务器。效果很好,但我不知道使用提供的哪些时间变量来正确设置计算机上的时钟。

阅读第 23 页的 NTP https://tools.ietf.org/html/rfc5905 的 IETF 文档,我选择了其中之一:

Transmit Timestamp (xmt):响应离开时服务器上的时间 对于客户端,采用 NTP 时间戳格式。

Destination Timestamp (dst):回复时在客户端的时间 从服务器到达,采用 NTP 时间戳格式。

我的解释是xmt是服务器发送时的正确时间,这似乎表明我仍然需要为从服务器到我的计算机的传输时间添加时间延迟?

我不清楚 dst 时间的定义。这可能意味着:

  1. 这是已添加传输时间的 xmt 时间,因此是用于设置时钟的正确时间
  2. 或者它是 NTP 数据包到达时我的时钟时间。如果我的时钟是错误的,那将不是正确的使用时间

是哪一个

我认为 #1(使用 dst)更有意义,但在网上找到的大多数脚本都使用普通的 xmt。就 ntplib 的代码而言,这意味着:

    client = ntplib.NTPClient()
    resp   = client.request(server,version=3)
    xmt    = resp.tx_time     # for the use of xmt
    # or:
    dst    = resp.dest_time   # for the use of dst

在一些测试运行中,dst 总是比 xmt 晚 3 ... 30 毫秒,在使用本地、区域或全球 NTP 服务器时没有明显的模式。

所以不是很多,但我不想做出不合逻辑的选择。

解决方法

NTP 响应有 5 个时间戳

字段名称 姓名 说明
ref_timestamp 参考时间戳 系统时钟上次设置或更正的时间,采用 NTP 时间戳格式
orig_timestamp 原始时间戳(T1) 请求离开服务器时在客户端的时间,采用 NTP 时间戳格式。
recv_timestamp 接收时间戳(T2) 请求从客户端到达服务器的时间,采用 NTP 时间戳格式。
tx_timestamp 传输时间戳(T3) 响应离开客户端时服务器上的时间,采用 NTP 时间戳格式。
dest_timestamp 目的地时间戳(T4) 响应从服务器到达时在客户端的时间,采用 NTP 时间戳格式。

每个时间戳字段都有一个关联的时间字段,该字段是作为系统时间的时间戳,即自纪元以来的时间(以秒为单位)。

字段名称 说明
ref_time 参考时间戳作为系统时间
原点时间 将时间戳作为系统时间
recv_time 接收时间戳作为系统时间
tx_time 传输时间戳作为系统时间
dest_time 目标时间戳作为系统时间

偏移量是您的本地时钟与 NTP 服务器时钟之间的差值,即以秒为单位校正本地时钟以匹配服务器时钟的量。

偏移量 = ((recv_timestamp - orig_timestamp) + (tx_timestamp - dest_timestamp))/2

偏移量 = ((T2 - T1) + (T3 - T4))/2

示例:

import ntplib
from datetime import datetime

client = ntplib.NTPClient()
server = '0.pool.ntp.org'
resp = client.request(server,version=3)

print("offset",resp.offset)
print("orig_time:",datetime.utcfromtimestamp(resp.orig_time).strftime('%Y-%d-%m %H:%M:%S.%f'))
print("recv_time:",datetime.utcfromtimestamp(resp.recv_time).strftime('%Y-%d-%m %H:%M:%S.%f'))
print("tx_time  :",datetime.utcfromtimestamp(resp.tx_time).strftime('%Y-%d-%m %H:%M:%S.%f'))

输出

offset 0.009677410125732422
orig_time: 2021-06-01 00:06:10.553593
recv_time: 2021-06-01 00:06:10.665957
tx_time  : 2021-06-01 00:06:10.665980

偏移为正值意味着服务器时钟比本地时钟提前该秒数。负值表示本地时钟提前。

在上面的输出中,服务器在 00:06:10.665957 (recv_time) 收到时间请求,并在 00:06:10.665980 (tx_time) 响应,耗时 23 微秒完成。

,

所有各种时间戳的知识对于理解如何更改我的计算机的时间以与 NTP 时间服务器一致并不是很有帮助。

经过几天的摆弄,我相信我找到了解决方案。答案是:不要使用任何时间戳——而是只使用偏移量!并且偏移量已经由 ntplib 为您计算出来了。

这令人惊讶,因为大多数人使用其中一个时间戳(主要是 xmt),而且,我可能错过了它,但我从未见过有人使用偏移量。

让我们看一个例子。我使用了 3 个 NTP 服务器:一个在亚洲(最偏远),一个在欧洲(中距离),一个在德国(最近)。数据包的往返时间变化很大,从几毫秒到 200 毫秒不等,与近距离或远距离目标无关。我选择了一个例子来证明我的观点(代码在最后)。我从时间戳中减去原点,所以所有时间都相对于它们各自的原点。

    NTP Server            Type t-stamp          sec 
    asia.pool.ntp.org     orig                  0.000
    asia.pool.ntp.org     recv                  0.004
    asia.pool.ntp.org     tx                    0.004
    asia.pool.ntp.org     dest                  0.014
    asia.pool.ntp.org     offset               -0.003

    europe.pool.ntp.org   orig                  0.000
    europe.pool.ntp.org   recv                  0.008
    europe.pool.ntp.org   tx                    0.008
    europe.pool.ntp.org   dest                  0.020
    europe.pool.ntp.org   offset               -0.002

    de.pool.ntp.org       orig                  0.000
    de.pool.ntp.org       recv                  0.008
    de.pool.ntp.org       tx                    0.008
    de.pool.ntp.org       dest                  0.019
    de.pool.ntp.org       offset               -0.002

在亚洲示例中,往返总时间为 14 毫秒。时钟差在发送时为 4ms,在接收时为 10ms。因此偏移量为 (4 - 10)/2 = -3ms。

现在要做的所有重要假设是往返的旅行时间相同!那么我可以说如果我把这个偏移量加到我的电脑时间里,两个时间都会变得一样!

欧洲同样适用:往返 20 毫秒,偏移:(8 - 12)/2 = -2 毫秒,德国:往返 19 毫秒,偏移:(8 - 11)/2 = -1.5 毫秒。

由于偏移量已经由 lib 给出,所以您要做的就是将这个有符号的偏移量添加(!,而不是减去)到您的计算机时间。

然后我在使用德国(最近的)NTP 服务器大约半小时时记录了 ntplib 偏移量:

enter image description here

'Ambient' 是 ntplib 报告的偏移量,以毫秒为单位。

看起来我的电脑设置得很好,它的时间大多在 NTP 时间的 5 毫秒内。但是有一些长达 70 毫秒的显着偏移!在一个程序中,我本可以选择这个 - 显然是错误的 - 时间。

明显的问题:如何确保在我的时钟调整中不会出现异常值?

代码的基本部分:

    for i,ntpserver in enumerate(NTP_SERVERS):
    ntps     = []
    client   = ntplib.NTPClient()
    try:
        response = client.request(ntpserver,version=4,timeout=0.5) # latest NTP version = 4

        orig = response.orig_time
        recv = response.recv_time
        tx   = response.tx_time
        dest = response.dest_time
        offs = response.offset
        ntps.append( ["orig",orig - orig])
        ntps.append( ["recv",recv - orig])
        ntps.append( ["tx  ",tx   - orig])
        ntps.append( ["dest",dest - orig])
        ntps.append( ["offset",offs])
       
        for  a in ntps:
            print("{:20s}  {:15s}  {:10.3f} ".format(ntpserver,a[0],a[1]))
       
    except Exception as e:
        msg = fncname + "FAILED with Exception: {}".format(e)
        edprint(msg)