基于Ubantu的UDP套接字编程获取NTP网络时间(C语言版)

时间:2022-10-31 11:01:27

    UDP协议传输数据包并不可靠,但却比TCP更加高效。UDP协议在实时性要求较高的网络编程中得到了大量应用。其本身存在的不可靠性也可以通过应用层的相关协议来消除。

    在刚刚基本了解Linux的网络编程后,昨天仿照着书上的例程编写了一个使用UDP协议获取NTP网络时间的小程序,并且测试通过。想着把程序贴出来,以后没准也能用得上。

    要从NTP获取网络时间需要解析NTP的数据包,先打包一个NTP数据包上传给NTP服务器用于请求网络时间,NTP服务器随后会将时间数据通过数据包返回,在等待接收NTP返回数据包的过程中,应该使用select()方法对套接字文件描述符进行非阻塞检测是否有数据可读可写,当在指定时间内NTP返回了数据包,则select()方法会返回可读可写的套接字文件描述符个数,超时则自动返回0,select()方法在网络编程中很常用,而其使用方法在网上也很多,这里就不再介绍了。

    下面是UDP编程获取NTP网络时间的C代码:

/* UDP network gets ntp time demo based on platform linux. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <unistd.h>
#include <time.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <sys/time.h>
#include <sys/ioctl.h>


#define TRUE				1
#define FALSE				0

#define NTP_PORT			123
#define NTP_PORT_STR		"123"
#define NTP_SERVER_ADDR		"61.135.250.78"
#define NTP_PCK_SIZE		48

#define NTPV1				1
#define NTPV2				2
#define NTPV3				3
#define NTPV4				4

#define JAN_1970			0x83AA7E80
#define NTP_FRAC(x)			(4294 * (x) + ((1981 * (x)) >> 11))
#define USEC(x)				(((x) >> 12) - 759 * ((((x) >> 10) + 32768) >> 16))

#define LI 					0
#define VN 					NTPV3
#define MODE				3 /* client mode */
#define STRATUM				0
#define POLL				4
#define	PREC				-6


typedef struct _ntp_time {
	unsigned int coarse;
	unsigned int fine;
} ntp_time;


typedef struct _ntp_packet {
	unsigned char leap_ver_mode;
	unsigned char startnum;
	char poll;
	char precision;
	int root_delay;
	int root_dispersion;
	int reference_identifier;
	ntp_time reference_timestamp;
	ntp_time originage_timestamp;
	ntp_time receive_timestamp;
	ntp_time transmit_timestamp;
} ntp_pack;


int set_ntp_pack(char* packet) /* pack ntp network packet */
{
	long tmp_wrd;
	time_t timer;
	
	memset(packet, 0, NTP_PCK_SIZE);
	/* pack ntp head */
	tmp_wrd = htonl((LI << 30) | (VN << 27) | (MODE << 24) | 
						(STRATUM << 16) | (POLL << 8) | (PREC & 0XFF));
	memcpy(packet, &tmp_wrd, sizeof(tmp_wrd));
	
	/* set root delay and root dispersion and reference indentifier */
	tmp_wrd = htonl(1 << 16);
	memcpy(&packet[4], &tmp_wrd, sizeof(tmp_wrd));
	memcpy(&packet[8], &tmp_wrd, sizeof(tmp_wrd));
	
	/* set timer stamp */
	time(&timer);
	
	/* set transmit timestamp coarse */
	tmp_wrd = htonl(JAN_1970 + (long)timer);
	memcpy(&packet[40], &tmp_wrd, sizeof(tmp_wrd));
	
	/* set transmit timestamp fine */
	tmp_wrd = htonl((long) NTP_FRAC(timer));
	memcpy(&packet[44], &tmp_wrd, sizeof(tmp_wrd));
	
	return NTP_PCK_SIZE;
}


int get_ntp_time(int sockfd, struct addrinfo* addr, ntp_pack* time_now)
{
	char data[NTP_PCK_SIZE];
	int packet_len, addr_len = addr->ai_addrlen;
	fd_set read_fdset;
	struct timeval block_time;
	
	packet_len = set_ntp_pack(data);
	/* send packet to ntp server */
	if (sendto(sockfd, data, packet_len, 0, addr->ai_addr, addr_len) < 0) {
		printf ("send packet to ntp server failed\n");
		return FALSE;
	}
	
	FD_ZERO(&read_fdset);
	FD_SET(sockfd, &read_fdset); /* add fd into fdset */
	//FD_CLR(int fd, fd_set *fdset); /* remove fd from fdset */
	//FD_ISSET(int fd, fd_set *fdset); /* test fd of fdset */
	
	/* wait block for max 10 seconds */
	block_time.tv_sec = 10;
	block_time.tv_usec = 0;
	
	/* ntp data received or not */
	if (select(sockfd + 1, &read_fdset, NULL, NULL, &block_time) > 0) {
		/* receive packet from ntp server */
		if (recvfrom(sockfd, data, NTP_PCK_SIZE, 0, addr->ai_addr, &addr_len) < NTP_PCK_SIZE) {
			printf ("receive packet from ntp server failed\n");
			return FALSE;
		}
		
		/* read ntp server time packet */
		time_now->leap_ver_mode = ntohl(data[0]);
		time_now->startnum = ntohl(data[1]);
		time_now->poll = ntohl(data[2]);
		time_now->precision = ntohl(data[3]);
		
		 /* read integer type for 4 bytes */
		time_now->root_delay = ntohl(*((int *)&data[4]));
		time_now->root_dispersion = ntohl(*((int *)&data[8]));
		time_now->reference_identifier = ntohl(*((int *)&data[12]));
		time_now->reference_timestamp.coarse = ntohl(*((int *)&data[16]));
		time_now->reference_timestamp.fine = ntohl(*((int *)&data[20]));
		time_now->originage_timestamp.coarse = ntohl(*((int *)&data[24]));
		time_now->originage_timestamp.fine = ntohl(*((int *)&data[28]));
		time_now->receive_timestamp.coarse = ntohl(*((int *)&data[32]));
		time_now->receive_timestamp.fine = ntohl(*((int *)&data[36]));
		time_now->transmit_timestamp.coarse = ntohl(*((int *)&data[40]));
		time_now->transmit_timestamp.fine = ntohl(*((int *)&data[44]));
		
		return TRUE;
	}
	return FALSE;
}


int set_local_time(ntp_pack* newtime)
{
	struct timeval tv;
	
	tv.tv_sec = newtime->transmit_timestamp.coarse - JAN_1970;
	tv.tv_usec = USEC(newtime->transmit_timestamp.fine);
	return settimeofday(&tv, NULL);
}


int main (int argc, char* argv[])
{
	int sockfd;
	struct addrinfo hints, *res = NULL;
	ntp_pack nt_pack;
	
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM; /* udp socket type */
	hints.ai_protocol = IPPROTO_UDP;
	
	/* get ntp information */
	if (getaddrinfo(NTP_SERVER_ADDR, NTP_PORT_STR, &hints, &res)) {
		printf ("get ntp information failed\n");
		exit(0);
	}
	
	sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
	if (sockfd < 0) {
		printf ("create new socket failed\n");
		exit(1);
	}
	
	if (get_ntp_time(sockfd, res, &nt_pack)) {
		printf ("get ntp server time success\n");
		if (!set_local_time(&nt_pack)) { /* set local time */
			printf ("set local time success\n");
		}
	}
	
	close(sockfd); /* close udp socket */
	return TRUE;
}