NTP服务

作者: 小鱼儿他老汉 | 来源:发表于2018-04-10 10:07 被阅读194次

    NTP是最常见的协议,按理来说不应该再花精力去了解,直接调用库即可。可是在ESP8266上的MicroPython NTP库居然出错。所以不得不花些精力。

    在我自己的EPIC服务器中,每台设备登陆时服务器都会自动授时,方便设备校对时间,做后续的加密同步。这是私有协议,我比较倾向于由设备向第三方授时服务器进行同步。比较好奇现有的NTP服务器是如何工作的。所以查阅了三个源码和两个文档:

    1. pip上的ntplib源码;
    2. MicroPython的untplib源码;
    3. Arduino ntp源码;
    4. RFC1305文档
    5. 东北大学NTP授时中心文档

    NTP协议报文

    image

    研究了半天,Arduino的ntp源码是最简化的,而MicroPython的untplib直接抄袭自CPython ntplib,而且存在问题。所以按照Arduino的风格重新测试了一下,返回结果正常。

    NTP的UDP请求报文中或许只有前4个字节是有意义的。而返回报文中只有[41:43]字节是有意义的,返回的是1900-01-01 00:00:00到目前的格林威治时间的秒和微秒(小数点后几位)。所以还需要做个减法,因为一般Unix时间戳是从1970-01-01 00:00:00开始计时。这个减法需要将闰年计算在内。加减法做完之后,计算出年月日,然后是乘除法,计算出当前时分秒。

    至于中间延时,对于一般网络授时可以忽略不计了。要原子钟精度的请使用GPS授时。

    NTP的报文可以固化下来,并最简化,把请求固化在ROM中,把接收到的报文取出四个字节,做减法和除法就好了。

    CPython测试代码

    一堆调试信息,我就不删除了。对于MicroPython来说,甚至不需要binascii调试,使用struct.unpack就足矣。

    #!/usr/bin/env python  
      
    from socket import *  
    import binascii
    import struct
    import datetime
    
    HOST='0.uk.pool.ntp.org'  
    PORT=123  
    
    _PACKET_FORMAT = "!BBBbIIIIIIIIIII"
    
    def asmNtpRequest():
        buf = bytearray(48)
        buf[0] = 0b11100011 # Lead, ... version
        buf[1] = 0     # Stratum, or type of clock
        buf[2] = 6     # Polling Interval
        buf[3] = 0xEC  # Peer Clock Precision
          # 8 bytes of zero for Root Delay & Root Dispersion
        #buf[12]  = 49 # Reference Id, could be any number, none-zero
        #buf[13]  = 0x4E
        #buf[14]  = 49
        #buf[15]  = 52
        buf[12] = 1
        buf[13] = 2
        buf[14] = 3
        buf[15] = 4
    
        print("request-size:",len(buf))
        print("request-buf:",binascii.hexlify(buf).upper())
        return buf
    
    def parseNtpResponse(data):
        print(binascii.hexlify(data[40:44]))
    
        try:
            unpacked = struct.unpack(_PACKET_FORMAT,
                        data[0:struct.calcsize(_PACKET_FORMAT)])
        except struct.error:
            print("struct unpack error")
    
        for b in unpacked:
            print(b),hex(b)
    
        ntp = unpacked[-2]
        print(ntp, hex(ntp))    
    
        ntp = ntp - 2208988800
        print("ntp_new", ntp, hex(ntp))
        print(datetime.datetime.fromtimestamp(ntp))
    
    setdefaulttimeout(5)
    
    s = socket(AF_INET,SOCK_DGRAM)  
    s.connect((HOST,PORT))  
    
    message = asmNtpRequest()
    s.send(message)
    data = s.recv(48)  
    print("response-size:",len(data))
    print("response-hex:",binascii.hexlify(data).upper())
    parseNtpResponse(data)
    
    s.close()
    

    以上只是在CPython上通过,稍后提供MicroPython的代码,可能会叫nntplib.py。即nano-ntplib。

    MicroPython代码

    MicroPython一些库看着与CPython类似,其实暗坑不少。

    from socket import *
    import ubinascii
    import ustruct
    #import datetime
    import utime
    
    HOST='0.uk.pool.ntp.org'  
    PORT=123  
    
    _PACKET_FORMAT = "!BBBbIIIIIIIIIII"
    
    def do_connect():
        import network
        sta_if = network.WLAN(network.STA_IF)
        if not sta_if.isconnected():
            print('connecting to WiFi...')
            sta_if.active(True)
            sta_if.connect('CMCC-302', 'Kirin20110606')
            while not sta_if.isconnected():
                pass
        print('ifconfig:', sta_if.ifconfig())
        return sta_if
    
    def asmNtpRequest():
        buf = bytearray(48)
        buf[0] = 0b11100011
        buf[1] = 0     # Stratum, or type of clock
        buf[2] = 6     # Polling Interval
        buf[3] = 0xEC  # Peer Clock Precision
          # 8 bytes of zero for Root Delay & Root Dispersion
        #buf[12]  = 49 # Reference Id
        #buf[13]  = 0x4E
        #buf[14]  = 49
        #buf[15]  = 52
        buf[12] = 1
        buf[13] = 2
        buf[14] = 3
        buf[15] = 4
    
        print("request-size:",len(buf))
        print("request-buf:",ubinascii.hexlify(buf).upper())
        return buf
    
    def parseNtpResponse(data):
        print(ubinascii.hexlify(data[40:44]))
    
        try:
            unpacked = ustruct.unpack(_PACKET_FORMAT,
                        data[0:ustruct.calcsize(_PACKET_FORMAT)])
        except ustruct.error:
            print("struct unpack error")
    
        for b in unpacked:
            print(b),hex(b)
    
        ntp = unpacked[-2]
        print(ntp, hex(ntp))    
    
        ntp = ntp - 2208988800
        print("ntp_new", ntp, hex(ntp))
        #print(datetime.datetime.fromtimestamp(ntp))
        print(utime.localtime(ntp))
        print("CPython/MicroPython has different result of time.localtime()")
    
    def main():
        do_connect()
    
        #setdefaulttimeout(10)
    
        s = socket(AF_INET,SOCK_DGRAM)  
        #addrinfo = getaddrinfo(HOST, PORT)
        #addr = addrinfo[0][-1]
        #s.connect(addr)  
        s.connect(('194.80.204.184',123))
        #s.settimeout(5)
    
        message = asmNtpRequest()
        #s.sendall(message)
        s.send(message)
        data = s.recv(48)  
        print("response-size:",len(data))
        print("response-hex:",ubinascii.hexlify(data).upper())
        parseNtpResponse(data)
    
        s.close()
    
    main()
    

    测试下来,CPython/MicroPython在time.localtime()返回时间元组居然差了30年。而且MicroPython for Pyboard/ESP8266都是如此。可能是个Bug。目前依然用Arduino的C++代码移植了一个简单的转换函数。

    以上代码加上LED的SPI驱动,做个网络授时时钟完全可行,还可以在MicroPython上构建本地NTP服务,不过这并不重要。希望能够在此基础上构建IFTTT和其他的时间触发服务。

    相关文章

      网友评论

        本文标题:NTP服务

        本文链接:https://www.haomeiwen.com/subject/vnrghftx.html