1. 程式人生 > >簡單的NTP客戶端實現

簡單的NTP客戶端實現

NTP(Network Time Protocol,網路時間協議)是由RFC 1305定義的時間同步協議,用來在分散式時間伺服器和客戶端之間進行時間同步。NTP基於UDP報文進行傳輸,使用的UDP埠號為123。

使用NTP的目的是對網路內所有具有時鐘的裝置進行時鐘同步,使網路內所有裝置的時鐘保持一致,從而使裝置能夠提供基於統一時間的多種應用。

對於執行NTP的本地系統,既可以接收來自其他時鐘源的同步,又可以作為時鐘源同步其他的時鐘,並且可以和其他裝置互相同步。

NTP工作原理

NTP的基本工作原理如所示。Device A和Device B通過網路相連,它們都有自己獨立的系統時鐘,需要通過NTP實現各自系統時鐘的自動同步。為便於理解,作如下假設:

在Device A和Device B的系統時鐘同步之前,Device A的時鐘設定為10:00:00am,Device B的時鐘設定為11:00:00am。

 Device B作為NTP時間伺服器,即Device A將使自己的時鐘與Device B的時鐘同步。

   NTP報文在Device A和Device B之間單向傳輸所需要的時間為1秒。

NTP技術 - yu - sms

       系統時鐘同步的工作過程如下:

   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)。

至此,Device A已經擁有足夠的資訊來計算兩個重要的引數:

 NTP報文的往返時延Delay=(T4-T1)-(T3-T2)=2秒。

  Device A相對Device B的時間差offset=((T2-T1)+(T3-T4))/2=1小時。

這樣,Device A就能夠根據這些資訊來設定自己的時鐘,使之與Device B的時鐘同步。

NTP的報文格式

NTP有兩種不同型別的報文,一種是時鐘同步報文,另一種是控制報文。控制報文僅用於需要網路管理的場合,它對於時鐘同步功能來說並不是必需的,這裡不做介紹。

NTP技術 - yu - sms

主要欄位的解釋如下:

l              LI(Leap Indicator):長度為2位元,值為“11”時表示告警狀態,時鐘未被同步。為其他值時NTP本身不做處理。

l              VN(Version Number):長度為3位元,表示NTP的版本號,目前的最新版本為3。

l              Mode:長度為3位元,表示NTP的工作模式。不同的值所表示的含義分別是:0未定義、1表示主動對等體模式、2表示被動對等體模式、3表示客戶模式、4表示伺服器模式、5表示廣播模式或組播模式、6表示此報文為NTP控制報文、7預留給內部使用。

l              Stratum:系統時鐘的層數,取值範圍為1~16,它定義了時鐘的準確度。層數為1的時鐘準確度最高,準確度從1到16依次遞減,層數為16的時鐘處於未同步狀態,不能作為參考時鐘。

l              Poll:輪詢時間,即兩個連續NTP報文之間的時間間隔。

l              Precision:系統時鐘的精度。

l              Root Delay:本地到主參考時鐘源的往返時間。

l              Root Dispersion:系統時鐘相對於主參考時鐘的最大誤差。

l              Reference Identifier:參考時鐘源的標識。

l              Reference Timestamp:系統時鐘最後一次被設定或更新的時間。

l              Originate Timestamp:NTP請求報文離開發送端時傳送端的本地時間。

l              Receive Timestamp:NTP請求報文到達接收端時接收端的本地時間。

l              Transmit Timestamp:應答報文離開應答者時應答者的本地時間。

l              Authenticator:驗證資訊。

NTP的工作模式

 裝置可以採用多種NTP工作模式進行時間同步:

             客戶端/伺服器模式

             對等體模式

            廣播模式

            組播模式

使用者可以根據需要選擇合適的工作模式。在不能確定伺服器或對等體IP地址、網路中需要同步的裝置很多等情況下,可以通過廣播或組播模式實現時鐘同步;客戶端/伺服器和對等體模式中,裝置從指定的伺服器或對等體獲得時鐘同步,增加了時鐘的可靠性。

以上資訊來自於http://blog.163.com/yzc_5001/blog/static/2061963420121283050787/

下面是NTP客戶端實現程式碼:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SUB70 0x83aa7e80U		//1900-1970之間的時間差
#define NTP_PORT 123
#define NTP_SERVER "120.24.166.46"

#define uint32 unsigned int
#define uint64 unsigned long long

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


typedef struct _ntppack
{
	uint32 li_vn;		    //LI VN MODE STRATUM POLL PRECISION
	uint32 root_delay;	    //本地到主參考時鐘源的往返時間
	uint32 root_dispersion;		//系統時鐘相對於主參考時鐘的最大誤差
	uint32 reference_identifier;  //參考時鐘源標識

	uint64 reference_time;    //T4  系統時間最後一次被改變
	uint64 originate_time;    //T1  離開發送端時傳送端的時間
	uint64 receive_time;      //T2  到達伺服器時伺服器的時間
	uint64 transmit_time;     //T3  離開伺服器時伺服器的時間
}NTP;

NTP send_pack,recv_pack;
uint64 firsttime,finaltime;
uint64 delaytime,diftime;

void init(void)
{
	send_pack.li_vn = htonl((LI << 30)|(VN << 27)|(MODE << 24)|(STRATUM << 16)|(POLL << 8)|(PREC & 0xff));
	firsttime = (long long)time(NULL)+SUB70;

	send_pack.reference_time = htonl(firsttime);
}

int main(void)
{
	int sock;
	struct sockaddr_in addr;
	struct timeval tv1;

	if((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
	{
		perror("socket");
		exit(1);
	}
	
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(NTP_PORT);
	addr.sin_addr.s_addr = inet_addr(NTP_SERVER);

	init();

	sendto(sock, &send_pack, sizeof(send_pack), 0, (struct sockaddr *)&addr, sizeof(struct sockaddr));

	recv(sock, &recv_pack, sizeof(recv_pack), 0);<span style="white-space:pre">	</span>//block

	/****轉換位元組序****/
	recv_pack.root_delay = ntohl(recv_pack.root_delay);
	recv_pack.root_dispersion = ntohl(recv_pack.root_dispersion);
	recv_pack.reference_identifier = ntohl(recv_pack.reference_identifier);
	recv_pack.reference_time = ntohl(recv_pack.reference_time);
	recv_pack.originate_time = ntohl(recv_pack.originate_time);
	recv_pack.receive_time = ntohl(recv_pack.receive_time);
	recv_pack.transmit_time = ntohl(recv_pack.transmit_time);

	diftime = ((recv_pack.receive_time - firsttime) +(recv_pack.transmit_time - finaltime)) >> 1;
	
	delaytime = ((finaltime - firsttime) - (recv_pack.transmit_time - recv_pack.receive_time)) >> 1;
	
	tv1.tv_sec = time(NULL)+diftime+delaytime;
	tv1.tv_usec = 0;

	if(settimeofday(&tv1, NULL) < 0)
	{
		perror("settimeofday");
		exit(1);
	}

	system("date");



	return 0;
}