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);
}