您的位置:首页 > 编程语言 > PHP开发

模拟NTP协议实现时钟同步

2011-11-12 20:59 746 查看
Network Time Protocol(NTP)是用来使计算机时间同步化的一种协议,它可以使计算机对其服务器或时钟源(如石英钟,GPS等等)做同步化,它可以提供高精准度的时间校正(LAN上与标准间差小于1毫秒,WAN上几十毫秒),且可介由加密确认的方式来防止恶毒的协议攻击。

NTP提供准确时间,首先要有准确的时间来源,这一时间应该是国际标准时间UTC。 NTP获得UTC的时间来源可以是原子钟、天文台、卫星,也可以从Internet上获取。这样就有了准确而可靠的时间源。时间是按NTP服务器的等级传播。按照距离外部UTC 源的远近将所有服务器归入不同的Stratun(层)中。Stratum-1在顶层,有外部UTC接入,而Stratum-2则从Stratum-1获取时间,Stratum-3从Stratum-2获取时间,以此类推,但Stratum层的总数限制在15以内。所有这些服务器在逻辑上形成阶梯式的架构并相互连接,而Stratum-1的时间服务器是整个系统的基础。

进行网络协议实现时最重要的是了解协议数据格式。NTP数据包有48个字节,其中NTP包头16字节,时间戳32个字节。其协议格式如下图所示。



其协议字段的含义如下所示。

LI:跳跃指示器,警告在当月最后一天的最终时刻插入的迫近闺秒(闺秒)。

VN:版本号。

Mode:工作模式。该字段包括以下值:0-预留;1-对称行为;3-客户机;4-服务器;5-广播;6-NTP控制信息。NTP协议具有3种工作模式,分别为主/被动对称模式、客户/服务器模式、广播模式。在主/被动对称模式中,有一对一的连接,双方均可同步对方或被对方同步,先发出申请建立连接的一方工作在主动模式下,另一方工作在被动模式下;客户/服务器模式与主/被动模式基本相同,惟一区别在于客户方可被服务器同步,但服务器不能被客户同步;在广播模式中,有一对多的连接,服务器不论客户工作在何种模式下,都会主动发出时间信息,客户根据此信息调整自己的时间。

Stratum:对本地时钟级别的整体识别。

Poll:有符号整数表示连续信息间的最大间隔。

Precision:有符号整数表示本地时钟精确度。

Root Delay:表示到达主参考源的一次往复的总延迟,它是有15~16位小数部分的符号定点小数。

Root Dispersion:表示一次到达主参考源的标准误差,它是有15~16位小数部分的无符号定点小数。

Reference Identifier:识别特殊参考源。

Originate Timestamp:这是向服务器请求分离客户机的时间,采用64位时标格式。

Receive Timestamp:这是向服务器请求到达客户机的时间,采用64位时标格式。

Transmit Timestamp:这是向客户机答复分离服务器的时间,采用64位时标格式。

Authenticator(Optional):当实现了NTP认证模式时,主要标识符和信息数字域就包括已定义的信息认证代码(MAC)信息。
---------------------------------------------------------------------------------------------------------------------------
以上对NTP协议原理的描述来自网络,感谢网络上的朋友!
以下是我写的模拟NTP协议进行时钟同步的Demo程序。完整的代码可从这里下载: http://download.csdn.net/detail/chexlong/3787414
NTP.h
#ifndef NTP_H
#define NTP_H

#include <winsock2.h>
#include <stdio.h>
#include <time.h>
#pragma comment(lib, "WS2_32")	// 链接到WS2_32.lib

#define LI 0
#define VN 3
#define MODE 3
#define STRATUM 0
#define POLL 4
#define PREC -6

#define JAN_1970      0x83aa7e80      /* 2208988800 1970 - 1900 in seconds */
#define NTPFRAC(x) (4294 * (x) + ((1981 * (x))>>11))
#define USEC(x) (((x) >> 12) - 759 * ((((x) >> 10) + 32768) >> 16))

typedef struct ntptime_t
{
unsigned int coarse;
unsigned int fine;
}ntptime;

typedef struct ntp_packet_t
{
//header
unsigned int leap_year_indicator:2;
unsigned int version_number:3;
unsigned int mode:3;
unsigned int stratum :8;
unsigned int poll :8;
unsigned int precision :8;

unsigned int root_delay;
unsigned int root_dispersion;
unsigned int reference_identifier;

//时间戳
ntptime  reference_timestamp; //T4
ntptime originate_timestamp;  //T1
ntptime receive_timestamp;    //T2
ntptime transmit_timestamp;   //T3
}ntp_packet;

typedef union
{
ntp_packet ntp;
char c[48];
}NTP_PACKET_T;

void GetNTPTime(ntptime *ntpTime)
{
SYSTEMTIME sysTime;
FILETIME fileTime;
GetSystemTime(&sysTime);
SystemTimeToFileTime(&sysTime,&fileTime);
ntpTime->coarse = fileTime.dwHighDateTime;
ntpTime->fine = fileTime.dwLowDateTime;
}

SOCKET CreateSock(char *psLocalIP, unsigned short usLocalPort, unsigned int iTimeOut)
{
//创建套节字
SOCKET sock = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if( INVALID_SOCKET == sock )
{
printf("Failed socket() %d \n", ::WSAGetLastError());
return 0;
}

if (0 != iTimeOut)
{
//设置套接字发送,接收超时时间
int iRet = setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&iTimeOut, sizeof(iTimeOut));
iRet = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&iTimeOut, sizeof(iTimeOut));
}

//绑定本地地址
sockaddr_in addrLocal;
memset(&addrLocal, 0, sizeof(addrLocal));
addrLocal.sin_family = AF_INET;
addrLocal.sin_addr.s_addr = htonl(INADDR_ANY);
addrLocal.sin_port = htons(usLocalPort);

if ( 0 != bind(sock, (struct sockaddr*)&addrLocal, sizeof(addrLocal)) )
{
printf("Failed bind() %d \n", ::WSAGetLastError());
closesocket(sock);
return 0;
}

return sock;
}

#endif


Base64.h
#ifndef BASE64_H
#define BASE64_H

const int pr2six[256]={
64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,62,64,64,64,63,
52,53,54,55,56,57,58,59,60,61,64,64,64,64,64,64,64,0,1,2,3,4,5,6,7,8,9,
10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,64,64,64,64,64,64,26,27,
28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,
64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
64,64,64,64,64,64,64,64,64,64,64,64,64
};

char six2pr[64] = {
'A','B','C','D','E','F','G','H','I','J','K','L','M',
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'a','b','c','d','e','f','g','h','i','j','k','l','m',
'n','o','p','q','r','s','t','u','v','w','x','y','z',
'0','1','2','3','4','5','6','7','8','9','+','/'
};

void Base64Decode(const char *bufcoded, char * dst,int *nbytes)
{
int nbytesdecoded;
const char *bufin = bufcoded;
unsigned char *bufout = (unsigned char*)dst;
int nprbytes;

/* Figure out how many characters are in the input buffer.
* If this would decode into more bytes than would fit into
* the output buffer, adjust the number of input bytes downwards.
*/
//bufin = bufcoded;
while(pr2six[(int)*(bufin++)] <= 63){}
nprbytes = (int)(bufin - bufcoded - 1);
nbytesdecoded = ((nprbytes+3)/4) * 3;

bufin = bufcoded;

while (nprbytes > 0)
{
*(bufout++) =
(unsigned char) (pr2six[(int)*bufin] << 2 | pr2six[(int)bufin[1]] >> 4);
*(bufout++) =
(unsigned char) (pr2six[(int)bufin[1]] << 4 | pr2six[(int)bufin[2]] >> 2);
*(bufout++) =
(unsigned char) (pr2six[(int)bufin[2]] << 6 | pr2six[(int)bufin[3]]);
bufin += 4;
nprbytes -= 4;
}

if(nprbytes & 03)
{
if(pr2six[(int)bufin[-2]] > 63)
nbytesdecoded -= 2;
else
nbytesdecoded -= 1;
}
dst[nbytesdecoded] = '\0';
if ( nbytes )
{
*nbytes = nbytesdecoded;
}
}

void Base64Encode(const unsigned char *bufin, unsigned int nbytes, char * dst)
{
unsigned char *outptr = (unsigned char *)dst;
unsigned int i;

for (i=0; i<nbytes; i += 3)
{
*(outptr++) = six2pr[*bufin >> 2];            /* c1 */
*(outptr++) = six2pr[((*bufin << 4) & 060) | ((bufin[1] >> 4) & 017)]; /*c2*/
*(outptr++) = six2pr[((bufin[1] << 2) & 074) | ((bufin[2] >> 6) & 03)];/*c3*/
*(outptr++) = six2pr[bufin[2] & 077];         /* c4 */

bufin += 3;
}

/* If nbytes was not a multiple of 3, then we have encoded too
* many characters.  Adjust appropriately.
*/
if(i == nbytes+1)
{
/* There were only 2 bytes in that last group */
outptr[-1] = '=';
}
else if(i == nbytes+2)
{
/* There was only 1 byte in that last group */
outptr[-1] = '=';
outptr[-2] = '=';
}

*(outptr++) = '\0';

//size_t len = outptr - 1 - (unsigned char*)dst;
}

#endif


NTPClient.cpp
#include "../Include/NTP.h"
#include "../Include/Base64.h"

//NTP请求包打包
int CreateNtpPacket(NTP_PACKET_T &ntpPacket)
{
static unsigned long ulNTPId = 0;
ulNTPId++;
char szBuf[1024];
int iDataLen;

ntpPacket.ntp.leap_year_indicator = LI;
ntpPacket.ntp.version_number = VN;
ntpPacket.ntp.mode = MODE;
ntpPacket.ntp.stratum = STRATUM;
ntpPacket.ntp.poll = POLL;
ntpPacket.ntp.precision = (PREC&0xFF);
ntpPacket.ntp.root_delay = htonl(1<<16);
ntpPacket.ntp.root_dispersion = htonl(1<<16);
ntpPacket.ntp.reference_identifier = 0;

ntpPacket.ntp.reference_timestamp.coarse = 0;
ntpPacket.ntp.reference_timestamp.fine = 0;
GetNTPTime(&ntpPacket.ntp.originate_timestamp);
ntpPacket.ntp.receive_timestamp.coarse = 0;
ntpPacket.ntp.receive_timestamp.fine = 0;
ntpPacket.ntp.transmit_timestamp.coarse = 0;
ntpPacket.ntp.transmit_timestamp.fine = 0;

return 0;
}

int NtpRequest(SOCKET &sock)
{
int iRet;
sock = CreateSock("127.0.0.1", 4567, 5000);
if ( 0 == sock )
{
printf("Failed create sock1 \n");
return -1;
}

//填写远程地址信息
sockaddr_in addrRemote;
addrRemote.sin_family = AF_INET;
addrRemote.sin_port = htons(5678);
addrRemote.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

while (true)
{
char szTimeValue[128];
char szbuf[128];
memset(szTimeValue,0,128);
memset(szbuf,0,128);

NTP_PACKET_T ntpPacket;
memset(&ntpPacket,0,sizeof(ntpPacket));
CreateNtpPacket(ntpPacket);

//Base64进行编码
Base64Encode((unsigned char *)ntpPacket.c, sizeof(ntpPacket), szTimeValue);

//发送NTP请求数据包
iRet = ::sendto(sock, szTimeValue, strlen(szTimeValue), 0, (sockaddr*)&addrRemote, sizeof(addrRemote));
if ( SOCKET_ERROR == iRet )
{
printf("Failed sendto() sock %d \n", ::WSAGetLastError());
closesocket(sock);
}
else
{
printf("Successed sendto sock = %d, data lenth = %d \n", sock, iRet);
}

memset(szTimeValue,0,sizeof(szTimeValue));
int nLen = sizeof(addrRemote);

//接收NTP请求应答消息
iRet = ::recvfrom(sock, szbuf, 128, 0, (sockaddr*)&addrRemote, &nLen);

if(iRet > 0)
{
szbuf[iRet] = '\0';
printf(" 接收到数据(%s):%s \n", ::inet_ntoa(addrRemote.sin_addr), szbuf);

//Base54解码
Base64Decode(szbuf, szTimeValue, 0);
if ( '\0' == szTimeValue[0] )
{
printf("time value is empty. \n");
closesocket(sock);
::WSACleanup();
return -1;
}
memset(&ntpPacket,0,sizeof(ntpPacket));
memcpy(ntpPacket.c,szTimeValue,sizeof(ntpPacket));

//设置本地系统时钟
SYSTEMTIME sysTime;
struct tm *tm1 = _localtime32((__time32_t*)&ntpPacket.ntp.transmit_timestamp.coarse);
sysTime.wYear = tm1->tm_year+1900;
sysTime.wMonth = tm1->tm_mon+1;
sysTime.wDay = tm1->tm_mday;
sysTime.wHour = tm1->tm_hour;
sysTime.wMinute = tm1->tm_min;
sysTime.wSecond = tm1->tm_sec;
sysTime.wMilliseconds = 0;
//SetLocalTime(&sysTime);

printf("NTP ok, %4d-%02d-%02d %02d:%02d:%02d %d \n", sysTime.wYear,sysTime.wMonth,sysTime.wDay,sysTime.wHour,sysTime.wMinute,sysTime.wSecond,sysTime.wMilliseconds);
}
else
{
printf("Failed recvfrom() sock %d \n", ::WSAGetLastError());
}

Sleep(5000);
}

}

int main(int argc, char* argv[])
{
int iRet;
WSADATA wsaData;
WORD sockVersion = MAKEWORD(2, 2);
if(::WSAStartup(sockVersion, &wsaData) != 0)
{
exit(0);
}

SOCKET sock;
iRet = NtpRequest(sock);
if (0 != iRet)
{
printf("ntp request failed. \n");
}

system("pause");

closesocket(sock);
::WSACleanup();

return 0;
}


NTPServer.cpp
#include "../Include/Base64.h"
#include "../Include/NTP.h"

int StartNtpServer(SOCKET &sock)
{
int iRet;
sock = CreateSock("127.0.0.1", 5678, 0);
if ( 0 == sock )
{
printf("Failed create sock1 \n");
return -1;
}

sockaddr_in addrRemote;

char szTimeValue[128];
char szbuf[128];
memset(szTimeValue,0,128);
memset(szbuf,0,128);
NTP_PACKET_T ntpPacket;

while (true)
{
memset(&addrRemote,0,sizeof(addrRemote));
int nLen = sizeof(addrRemote);

//接收来自客户端的NTP请求消息
iRet = ::recvfrom(sock, szbuf, 128, 0, (sockaddr*)&addrRemote, &nLen);
if(iRet > 0)
{
szbuf[iRet] = '\0';
printf(" 接收到数据(%s):%s \n", ::inet_ntoa(addrRemote.sin_addr), szbuf);
}

//base64解码消息
Base64Decode(szbuf, szTimeValue, 0);

//构造NTP请求应答数据包
memset(&ntpPacket,0,sizeof(ntpPacket));
memcpy(ntpPacket.c,szTimeValue,sizeof(ntpPacket));
ntpPacket.ntp.mode = 4;
ntpPacket.ntp.transmit_timestamp.coarse = _time32(NULL);
memset(szTimeValue,0,sizeof(szTimeValue));

//Base64编码NTP请求应答消息
Base64Encode((unsigned char *)ntpPacket.c,sizeof(NTP_PACKET_T),szTimeValue);

//填写远程地址信息
addrRemote.sin_family = AF_INET;
addrRemote.sin_port = htons(4567);
addrRemote.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

//发送NTP请求应答消息
iRet = ::sendto(sock, szTimeValue, strlen(szTimeValue), 0, (sockaddr*)&addrRemote, sizeof(addrRemote));
if ( SOCKET_ERROR == iRet )
{
printf("Failed sendto() sock %d \n", ::WSAGetLastError());
closesocket(sock);
return -1;
}
else
{
printf("Successed sendto sock = %d, data lenth = %d \n", sock, iRet);
}
}

return 0;
}

int main(int argc, char* argv[])
{
WSADATA wsaData;
WORD sockVersion = MAKEWORD(2, 2);
if(::WSAStartup(sockVersion, &wsaData) != 0)
{
exit(0);
}

SOCKET sock;
int iRet = StartNtpServer(sock);
if (0 != iRet)
{
printf("start ntp server failed. \n");
}

system("pause");
closesocket(sock);
::WSACleanup();
return 0;
}


上述代码代码在VC2005环境中已测试通过,以下是客户端和服务端程序截图:



客户端



服务端
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息