https://blog.csdn.net/haidscs/article/details/102733130
首先找一个可用的ntp服务器,这里以阿里的ntp服务器为例:ntp1.aliyun.com。
把域名的ip解析出来:
因为ntp服务器是udp协议,ip:120.25.115.20 端口号:123,格式是接收48个字节,第一个字节以0xa3(版本4) 、0x1b, (版本3)、0x13(版本2) 、0x0b(版本1),返回的数据中带有时间。
ntp协议的报文格式:
NTP报文格式如上图所示,它的字段含义参考如下:
LI 闰秒标识器,占用2个bit
VN 版本号,占用3个bits,表示NTP的版本号,现在为3
Mode 模式,占用3个bits,表示模式
stratum(层),占用8个bits
Poll 测试间隔,占用8个bits,表示连续信息之间的最大间隔
Precision 精度,占用8个bits,,表示本地时钟精度
Root Delay根时延,占用8个bits,表示在主参考源之间往返的总共时延
Root Dispersion根离散,占用8个bits,表示在主参考源有关的名义错误
Reference Identifier参考时钟标识符,占用8个bits,用来标识特殊的参考源
参考时间戳,64bits时间戳,本地时钟被修改的最新时间。
原始时间戳,客户端发送的时间,64bits。
接受时间戳,服务端接受到的时间,64bits。
传送时间戳,服务端送出应答的时间,64bits。
认证符(可选项)
转化举例:
把十六进制数转换十进制数,再减去1900-1970的时间差(2208988800秒)。0xE15C2D5B->3780914523 -2208988800=1571925723 时间转化工具出来 。补充:有些服务器需要加上北京时间差(东八区的时区 【8*60*60】)
以上是在Windows上验证。
下面以stm32获取ntp服务器的时间
#define NTP_TIMESTAMP_DELTA 2208988800ull
#define DEV_LAN_NTP_UDP_REV_BUFF_MAX 200
u8 rbuf[DEV_LAN_UDP_REV_BUFF_MAX+1]={0};
u16 rlen=0;
static struct udp_pcb *lwip_ntp_udp_pcb=0;
static unsigned char *lwip_ntp_udp_rev_buff=0;
static unsigned short lwip_ntp_udp_rev_buff_max=0;
static unsigned short *lwip_ntp_udp_rev_len=0;
//发送
err_t lwip_ntp_udp_ip_send(unsigned char *data, unsigned long len, unsigned char *send_ip, unsigned short send_port)
{
struct pbuf *send_pbuf;
unsigned long i=0;
struct ip_addr sip;
err_t err=0;
IP4_ADDR(&sip, send_ip[0], send_ip[1], send_ip[2], send_ip[3]);
if(lwip_ntp_udp_pcb == NULL)
return -1;
for (i=0; i<len; i+=200)
{
send_pbuf = pbuf_alloc(PBUF_RAW,200, PBUF_REF);
if (send_pbuf !=NULL)
{
send_pbuf->payload = (void*)&data[i];
if (i+200<=len) send_pbuf->tot_len = 200;
else send_pbuf->tot_len=len%200;
err=udp_sendto(lwip_ntp_udp_pcb, send_pbuf, &sip, send_port);
pbuf_free(send_pbuf);
if (err!=0) break;
}
}
return err;
}
//接收
void lwip_ntp_udp_rev(void *arg, struct udp_pcb *upcb, struct pbuf *p, struct ip_addr *addr, u16_t port)
{
struct pbuf *q=0;
if (p !=NULL)
{
lwip_ntp_udp_rev_addr = *addr;
lwip_ntp_udp_rev_port = port;
for (q=p; q!=NULL; q=q->next)
{
if ((q->tot_len+*lwip_ntp_udp_rev_len)<=lwip_ntp_udp_rev_buff_max)
{
if (q->tot_len>=1472)
{
memcpy(&lwip_ntp_udp_rev_buff[*lwip_ntp_udp_rev_len],q->payload,1472);
*lwip_ntp_udp_rev_len+=1472;
}
else
{
memcpy(&lwip_ntp_udp_rev_buff[*lwip_ntp_udp_rev_len],q->payload,q->tot_len);
*lwip_ntp_udp_rev_len+=q->tot_len;
}
}
else break;
}
pbuf_free(p);
}
else
{
udp_remove(upcb);
}
}
//创立udp连接
err_t lwip_ntp_udp_connect_init(unsigned short src_port)
{
err_t err=0;
struct ip_addr rmtipaddr;
lwip_ntp_udp_close();
lwip_ntp_udp_pcb = udp_new();
if (lwip_ntp_udp_pcb)
{
IP4_ADDR(&rmtipaddr,120,25,115,20);//将点分10进制IP转为4字节变量,因为IP控制块里面通过32位存储IP地址。
err = udp_bind(lwip_ntp_udp_pcb, IP_ADDR_ANY, src_port);//绑定本地的ip和端口
//err=udp_connect(lwip_ntp_udp_pcb,&rmtipaddr,123); // udp连接,NTP使用的是UDP和123端口
udp_recv(lwip_ntp_udp_pcb, lwip_ntp_udp_rev, NULL);
}
return err;
}
void lwip_ntp_udp_rev_init(unsigned char *rev_buff, unsigned short rev_buff_max, unsigned short *rev_len)
{
*rev_len=0;
lwip_ntp_udp_rev_buff=rev_buff;
lwip_ntp_udp_rev_buff_max=rev_buff_max;
lwip_ntp_udp_rev_len=rev_len;
}
void lwip_ntp_udp_close(void)
{
udp_remove(lwip_ntp_udp_pcb);
}
void main()
{
u8 buuf[48] = {0x1b};
u8 ip[4] ={120,25,115,20};
u32 NtpTime = 0;
lwip_ntp_udp_connect_init(666);//创建连接
lwip_ntp_udp_rev_init(rbuf,DEV_LAN_UDP_REV_BUFF_MAX,&rlen);//分配内存
lwip_ntp_udp_ip_send(buuf,48,ip,123);//向addr的123端口发送报文,NTP使用的是UDP和123端口
if(rlen>0)
{
NtpTime = rbuf[0]<<24 | rbuf[1]<<16 | rbuf[2]<<8 | rbuf[3];
NtpTime -= NTP_TIMESTAMP_DELTA;
}
while(1);
}