NTP服务

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和其他的时间触发服务。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 1 概述 linux服务器在提供服务时,要和其他机器进行请求的交互,实际生产环境中,可能因为时间不同步,导致了服务...
    ghbsunny阅读 1,230评论 0 1
  • 1.测试坏境 虚拟主机中安装的Centos 6.9安装的软件NTP 2.NTP原理 NTP(network tim...
    十二楼中月阅读 4,122评论 0 7
  • 当本机时间不准确时,我们需要对时间进行校准,那么我们就需要在互联网上找到一个可以提供我们准确时间的服务器然后通过一...
    经纶先生阅读 8,234评论 1 7
  • 1. NTP简介 NTP(Network Time Protocol,网络时间协议)是用来使网络中的各个计算机时间...
    think_lonely阅读 342评论 0 0
  • 青年人特有的哀愁感和暧昧感是毒药。 而年轻的肉体就是催化反应的针剂。 手指点过滑嫩细软的肌肤,掌覆于其上,滑过时有...
    希声孟极阅读 187评论 0 0