工作原理
SNTP协议采用客户/服务器工作方式,服务器通过接收GPS信号或自带的原子钟作为系统的时间基准,客户机通过定期访问服务器提供的时间服务获得准确的时间信息,并调整自己的系统时钟,达到网络时间同步的目的。
系统时钟同步的工作过程如下:
Device A发送一个NTP报文给Device B,该报文带有它离开Device A时的时间戳,该时间戳为10:00:00am(T1)。
当此NTP报文到达Device B时,Device B加上自己的时间戳,该时间戳为11:00:01am(T2)。
当此NTP报文离开Device B时,Device B再加上自己的时间戳,该时间戳为11:00:02am(T3)。
当Device A接收到该响应报文时,Device A的本地时间为10:00:03am(T4)。
NTP报文的往返时延Delay=(T4-T1)-(T3-T2)=2秒
Device A相对Device B的时间差offset=((T2-T1)+(T3-T4))/2=1小时
报文格式
SNTP协议是UDP协议的客户,它利用UDP的123端口提供服务,SNTP客户在设置请求信息时要把UDP目的端口设置为该值,源端口可以为任何非零值,服务器在响应信息中对这些值进行交换。同其它应用层协议一样,SNTP协议的数据通信也是按数据帧的格式进行。
LI:当前时间闰秒标志。字段长度为2位整数,只在服务器端有效。
LI=0:无警告;
LI=1:最后一分钟是61秒;
LI=2:最后一分钟是59秒;
LI=3:警告(时钟没有同步)
服务器在开始时,LI设置为3,一旦与主钟取得同步后就设置成其它值。
VN:版本号。字段长度为3位整数,当前版本号为4。
Mode:指示协议模式。字段长度为3位,取值定义为:
Mode=0:保留
Mode=1:对称主动;
Mode=2:对称被动;
Mode=3:客户;
Mode=4:服务器;
Mode=5:广播;
Mode=6:保留为NTP控制信息;
Mode=7:保留为用户定义;
在单播和多播模式,客户在请求时把这个字段设置为3,服务器在响应时把这个字段设置为4。在广播模式下,服务器把这个字段设置为5。
Stratum:指示服务器工作的级别,该字段只在服务器端有效,字段长度为8位整数。取值定义为:
Stratum=0:故障信息;
Stratum=1:一级服务器;
Stratum=2-15:二级服务器;
Stratum=16-255:保留;
Poll Interval:指示数据包的最大时间间隔,以秒为单位,作为2的指数方的指数部分,该字段只在服务器端有效。字段长度为8位整数,取值范围从4-17,即16秒到131,072秒。
Precision:指示系统时钟的精确性,以秒为单位,作为2的指数方的指数部分,该字段只在服务器端有效。字段长度为8位符号整数,取值范围从-6到-20。
Root Delay:指示与主时钟参考源的总共往返延迟,以秒为单位,该字段只在服务器端有效。字段长度为32位浮点数,小数部分在16位以后,取值范围从负几毫秒到正几百毫秒。
Root Dispersion:指示与主时钟参考源的误差,以秒为单位,该字段只在服务器端有效。字段长度为32位浮点数,小数部分在16位以后,取值范围从零毫秒到正几百毫秒。
Reference Identifier:指示时钟参考源的标记,该字段只在服务器端有效。对于一级服务器,字段长度为4字节ASCII字符串,左对齐不足添零。对于二级服务器,在IPV4环境下,取值为一级服务器的IP地址,在IPV6环境下,是一级服务器的NSAP地址。
Reference Timestamp:指示系统时钟最后一次校准的时间,该字段只在服务器端有效,以前面所述64位时间戳格式表示。
Originate Timestamp:指示客户向服务器发起请求的时间,以前面所述64位时间戳格式表示。
Transmit Timestamp:指示服务器向客户发时间戳的时间,以前面所述64位时间戳格式表示。
Authenticator(可选):当需要进行SNTP认证时,该字段包含密钥和信息加密码
基本工作过程
最常用工作模式-单播模式
SNTP服务器在初始化时,Stratum字段设置为0,LI字段设置为3,Mode 字段设置为3,Reference Identifier字段设置为ASCII字符“INIT”,所有时间戳信息设置为0;
一旦SNTP服务器与外部时钟源取得同步后,进入工作状态,Stratum字段设置为1,LI字段设置为0,Reference Identifier字段设置为外部时钟源的ASCII字符,如“GPS”,Precision字段设置为-6到-20之间的一个数值,通常设置为-16。VN字段设置为客户端请求信息包的VN字段值,Root Delay和Root Dispersion字段通常设置为0,Reference Timestamp字段设置为从外部时钟源最新取得的时间,Originate Timestamp字段设置为客户请求包的Transmit Timestamp字段值,Transmit Timestamp字段设置为服务器发出时间戳给客户的时间。
SNTP服务器在工作过程中,如果与外部时钟源失去同步,Stratum字段设置为0,Reference Identifier字段设置为故障原因的ASCII字符,如:“LOST”,此时客户收到这个信息时,要丢弃服务器发给它的时间戳信息。
两种时间戳转化的方法。
一、库函数实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
typedef unsigned int u32_t;
typedef unsigned char u8_t;
typedef struct
{
u32_t year;
u32_t month;
u32_t day;
u32_t hour;
u32_t minute;
u32_t second;
}realtime;
realtime unixTimeStamp_to_beijingTime(u32_t time)
{
time_t t_unix;
struct tm *lt;
realtime t_location; //本地时间
t_unix=time; //Unix时间+8小时为北京时间
lt=localtime(&t_unix);
t_location.year=lt->tm_year+1900;
t_location.month=lt->tm_mon+1;
t_location.day=lt->tm_mday;
t_location.hour =lt->tm_hour;
t_location.minute=lt->tm_min;
t_location.second=lt->tm_sec;
return t_location;
}
int main()
{
realtime temp;
u32_t timestamp;
printf("please input timestamp = ");
scanf("%d",×tamp);
temp = unixTimeStamp_to_beijingTime(timestamp);
printf("beijing time = %d/%d/%d %d:%02d:%02d \n", temp.year, temp.month, temp.day, temp.hour, temp.minute, temp.second);
return 0;
}
二、 编程运算实现
#include <stdio.h>
#include <stdbool.h>
static int DAYMS = 24*3600;
static int FOURYEARS = 365*3 + 366;
static int norMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
static int leapMonth[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
void getHMS(int times)
{
int nHour = times/3600;
int nMin = times%3600/60;
int nSec = times%3600%60;
printf("%d:%d:%d\n",nHour, nMin, nSec);
}
void getMonAndDay(int nDays, int *nMonth, int *nDay, bool IsLeapYear)
{
//printf("%d\n", nDays);
int i = 0;
int *pMonths = IsLeapYear?leapMonth:norMonth;
for( i = 0; i < 12; i++)
{
int nTemp = nDays - pMonths[i];
if(nTemp <= 0)
{
*nMonth = i + 1;
if(nTemp == 0)
{
*nDay = pMonths[i];
}
else
{
*nDay = nDays;
}
break;
}
nDays = nTemp;
//printf("%d\n", nDays);
}
}
int main()
{
int temp;
scanf("%d", &temp);
temp += 28800;
int nDays = temp/DAYMS + 1;
int nYear4 = nDays/FOURYEARS;
int nRemain = nDays%FOURYEARS;
int nDecYear = 1970 + nYear4 * 4;
int nDecMonth = 0;
int nDecDay = 0;
bool bLeapYear = false;
if(nRemain < 365)
{
}
else if(nRemain < 365 * 2)
{
nDecYear += 1;
nRemain -= 365;
}
else if(nRemain < 365 * 3)
{
nDecYear += 2;
nRemain -= (365 + 365);
}
else
{
nDecYear += 3;
nRemain -= (365 + 365 + 365);
bLeapYear = true;
}
getMonAndDay(nRemain, &nDecMonth, &nDecDay, bLeapYear);
printf("%d/%d/%d\n", nDecYear, nDecMonth, nDecDay);
getHMS(temp%DAYMS);
return 0;
}