在互联网上发布精确时间,要依靠一个协议,叫NTP(Network TimeProtocol)协议,中文一般称为网络时间协议,互联网上有许多时间服务器,负责提供精确时间。
1、NTP协议的基本原理
假定时间服务器是A,我们的机器是B,则同步过程如下进行:
t4和t1是以B的时间标准记录的时间戳,其差t4-t1表示整个消息传递过程所需要的时间间隔;t3和t2是以A机的时间标准记录的时间戳,其差t3-t2表明消息传递过程在A机逗留的时间,那么(t4-t1)-(t3-t2)应该就是信息包从B到A,再从A传回B的时间(中间刨去了在A机的逗留时间),如果假定信息包从A到B和从B到A所用的时间一样,那么,从A到B或者从B到A信息包的传送时间d为:
d = ((t4 - t1) - (t3 - t2))/2
假定B机相对于A机的时间误差是c,则有下列等式:
t2 = t1 + c + d
t4 = t3 - c + d
从以上三个等式可以解出B机的时间误差c为:
c = ((t2 - t1) + (t3 - t4)) / 2
如果一时没有转过来,可以自己在纸上画个图,在细细地琢磨一下,应该没有问题。
2、简单网络时间协议SNTPv4(Simple Network Time Protocol version 4)
SNTPv4由NTP改编而来,主要支持同步网络计算机时钟机制。SNTPv4没有改变NTP规范和原有实现过程,它是对NTP的进一步改进,支持以一种简单、无状态远程过程调用模式执行精确而可靠的操作,这类似于UDP/TIME协议。
3、SNTP(NTP)的协议结构
123
0 1 2 3 4 5 6 7 8 9 0 1 2 3 45 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|LI | VN |Mode| Stratum| Poll| Precision |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|RootDelay|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|RootDispersion|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|ReferenceIdentifier|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
||
|Reference Timestamp(64)|
||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
||
|Originate Timestamp(64)|
||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
||
|Receive Timestamp(64)|
||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
||
|Transmit Timestamp(64)|
||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Key Identifier (optional)(32)|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
||
||
|Message Digest (optional)(128)|
||
||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
有对协议感兴趣的读者,可以在下面地址下载SNTP协议进行深入的研究。
http://blog.hengch.com/specification/sntp.pdf
4、SNTP的实际实现
说起来一大堆,但实现起来其实并不像说的那么复杂。
根据SNTP协议第5节《SNTP ClientOperations》的说明,如果采用unicast模式,其目的就是为了获得准确时间的话,向服务器发送的请求包中,除了第一个字节以外,其他的SNTP字段都可以设为0,这正好与我们的目的相符,下面我们给出实现本机与时间服务器同步的源程序。
还有两个细节要说明,一是NTP实用的端口号是123,二是NTP的header中的字符顺序是big-endian,也就是NetworkByteOrder(详见另一篇博文:《在DOS下进行网络编程(下)》中的描述),而我们本机的字符顺序是little-endian,所以在收到服务器的回应后要注意转换。
为方便说明,源程序前面加了行号。
001 #include<stdio.h>
002 #include<dos.h>
003 #include<sys/socket.h>
004 #include<arpa/inet.h>
005 #defineSNTP_PORT 123
006 #define SNTP_SERVER"210.72.145.44" // China
007 //#defineSNTP_SERVER"192.43.244.18" // NIST :sometimes ok
008 //#defineSNTP_SERVER"192.5.41.40" // U.S NavalObservatory : ok
009 //#defineSNTP_SERVER "128.102.16.2"
010 #defineSNTP_EPOCH 86400U * (365U* 70U + 17U)
011 #defineSNTP_8HOUR 3600U * 8U
012 struct Sntp_Header {
013 unsigned char LiVnMode;
014 unsigned char Stratum;
015 unsigned char Poll;
016 unsigned char Precision;
017 long int RootDelay;
018 long int RootDispersion;
019 char RefID[4];
020 long int RefTimeInt;
021 long int RefTimeFraction;
022 long int OriTimeInt;
023 long int OriTimeFraction;
024 long int RecvTimeInt;
025 long int RecvTimeFraction;
026 long int TranTimeInt;
027 long int TranTimeFraction;
028 };
029 int main() {
030 int retValue;
031 fd_set readfds;
032 // Structure of SNTP
033 struct Sntp_Header sntpHeader;
034 struct Sntp_Header sntpHeader1;
035 struct Sntp_Header *p;
036 char *p1;
037 // vars of network
038 int sendSock;
039 struct sockaddr_in toAddr;
040 int addrLen;
041 char *pBuf;
042 long int OriTimeInt;
043 long int DestTimeInt;
044 long int difference;
045 unsigned char tempChar;
046 struct timeval tv;
047 struct date dateNow;
048 struct time timeNow;
049 sendSock = socket(AF_INET,SOCK_DGRAM, 0);
050 if (sendSock < 0){
051 printf("nsendSocketCreation Fail!");
052 return -1;
053 }
054toAddr.sin_family = AF_INET;
055toAddr.sin_port =htons(SNTP_PORT);
056 toAddr.sin_addr.s_addr =inet_addr(SNTP_SERVER);
057bzero(&(toAddr.sin_zero), 8);
058 addrLen = sizeof(struct sockaddr);
059 bzero(&sntpHeader,sizeof(struct Sntp_Header));
060 sntpHeader.LiVnMode = 0x1b;
061 OriTimeInt = time(0) + SNTP_EPOCH -SNTP_8HOUR;
062 retValue = sendto(sendSock,&sntpHeader, sizeof(struct Sntp_Header), 0,
(struct sockaddr *)&toAddr, addrLen);
063 printf("nSend %d chars.",retValue);
064 p1 = (char*)&sntpHeader;
065 pBuf = (char*)&sntpHeader1;
066printf("ntSent...tttttReceived...");
067 for (int j = 0; j < 12;j++) {
068FD_ZERO(&readfds);
069 FD_SET(sendSock,&readfds);
070 tv.tv_sec = 10;
071 tv.tv_usec = 0;
072 select(sendSock + 1,&readfds, NULL, NULL, &tv);
073 if (FD_ISSET(sendSock,&readfds)) {
074 retValue =recvfrom(sendSock,
&pBuf[j * 4],
4,
0,
(struct sockaddr *)&toAddr,
&addrLen);
075 } else{
076 printf("nDidnot Get information from time server");
077 return-1;
078 }
079 if (retValue<= 0 ) {
080printf("nReceiving Fail");
081 return-1;
082 }
083 printf("n");
084 for (int i = 0; i< retValue; i++) {
085printf("t%02x", (unsigned char)p1[i + j * 4]);
086 }
087 printf("t");
088 for (int i = 0; i< retValue; i++) {
089printf("t%02x", (unsigned char)pBuf[i + j * 4]);
090 }
091 }
092 for (int j = 4; j< 12; j++) {
093 tempChar = *(pBuf + j *4);
094 *(pBuf + j * 4) = *(pBuf +j * 4 + 3);
095 *(pBuf + j * 4 + 3) =tempChar;
096 tempChar = *(pBuf + j * 4+ 1);
097 *(pBuf + j * 4 + 1) =*(pBuf + j * 4 + 2);
098 *(pBuf + j * 4 + 2) =tempChar;
099 }
100 p = (struct Sntp_Header *)pBuf;
101 DestTimeInt = time((time_t *)NULL)+ SNTP_EPOCH - SNTP_8HOUR;
102 printf("nLocal TimeStamp = %lu",DestTimeInt);
103 printf("tRefTimeInt = %lu",p->RefTimeInt);
104 printf("nOriTimeInt = %lu",OriTimeInt);
105 printf("tRecvTimeInt = %lu",p->RecvTimeInt);
106 printf("nTranTimeInt = %lu",p->TranTimeInt);
107 difference =(p->RecvTimeInt - OriTimeInt) +(p->TranTimeInt - DestTimeInt);
108 difference = difference / 2;
109 printf("tdifference = %ld",difference);
110 tv.tv_usec = 0;
111 tv.tv_sec = time(0) +difference;
112getdate(&dateNow);
113gettime(&timeNow);
114 printf("nDate and Time Beforeadjusting : %04d-%02d-%02d%02d:%02d:%02d",
dateNow.da_year, dateNow.da_mon, dateNow.da_day,
timeNow.ti_hour, timeNow.ti_min, timeNow.ti_sec);
115 retValue =settimeofday(&tv);
116 if (retValue == 0){
117 printf("nSetting Timeok!");
118 } else{
119 printf("nSetting TimeFail!");
120 }
121getdate(&dateNow);
122gettime(&timeNow);
123 printf("nDate and Time afteradjusting : %04d-%02d-%02d%02d:%02d:%02dn",
dateNow.da_year, dateNow.da_mon, dateNow.da_day,
timeNow.ti_hour, timeNow.ti_min, timeNow.ti_sec);
124 return 0;
125 }
下面就程序的某些部分作一些说明。
至此,程序解释完毕,该程序在DOS6.22,DJGPPv2,WATT-32下编译通过并运行良好。
下一篇文章计划写如何在DOS对AC'97的声卡进行编程,会举一个实例,敬请期待。