网络NTP协议时间校对源码

  从科锐培训中断回来,好几个月都没写过博客,以后像在武汉这样学习的日子大概不会有了。最近三个月换了两份工作,也够折腾,期间有想过重回科锐的念头,种种原因最后放弃了。

  写这个NTP校时的程序,起因是家里旧笔记本的BIOS没电了,每次断电重启都要重设时间,于是就决定自己写一个。NTP服务器使用UDP协议和123端口号,查询时间的具体方法及NTP协议数据格式见源码及注释,还是比较简单的。代码中只是简单的读取服务器返回的时间,没有去计算修正网络传输的来回延时,对我来说精度已经足够了,有需求的朋友可以改一下。


  废话不多说,先上UDP类源码:


MyUDP.h:

#pragma once

#include 
#include 
#include 

#pragma comment(lib,"ws2_32.lib")

#define SAFE_CLOSESOCKET(socket) if(INVALID_SOCKET!=(socket)){closesocket((socket));(socket)=INVALID_SOCKET;}

typedef struct _PER_IO_DATA {
	WSAOVERLAPPED overlapped;
	WSABUF wsaBuf;
	int cbBytes;
	WSAEVENT wsaEvent;
}PER_IO_DATA,*PPER_IO_DATA;

class CMyUDP {
private:
	BOOL m_bInit;
	SOCKET m_socket;
	SOCKADDR_IN m_sockaddr;
	PER_IO_DATA m_PerIoData;

public:
	CMyUDP();
	~CMyUDP();

	// 初始化socket环境
	int InitWSAStartup(BYTE ucMajor,BYTE ucMinor);
	// 设置服务器信息
	BOOL SetServer(char *szIP,int nPort);
	// 发送数据
	int SendTo(char *buf,int nSize);
	// 接收数据
	int RecvFrom(char *buf,int nSize,DWORD dwMillisecords);
	// 完成例程回调函数
	static void CALLBACK CompletionRoutineFunc(
		DWORD dwError,
		DWORD cbTransferred,
		LPWSAOVERLAPPED lpOverlapped,
		DWORD dwFlags);
};

MyUDP.cpp

#include "MyUDP.h"

// 构造函数
CMyUDP::CMyUDP() {
	m_bInit = FALSE;
	m_socket = INVALID_SOCKET;
	memset(&m_sockaddr,0,sizeof(m_sockaddr));
	memset(&m_PerIoData,0,sizeof(m_PerIoData));
	
	m_PerIoData.wsaEvent = WSACreateEvent();

	InitWSAStartup(2,2);
}

// 析构函数
CMyUDP::~CMyUDP() {
	SAFE_CLOSESOCKET(m_socket);

	CloseHandle(m_PerIoData.wsaEvent);

	// 释放socket环境资源
	if(m_bInit) {
		WSACleanup();
	}
}

// 初始化socket环境
int CMyUDP::InitWSAStartup(BYTE ucMajor,BYTE ucMinor) {
	int nRet = 0;
	WSADATA wsaData = {0};

	nRet = WSAStartup(MAKEWORD(ucMajor,ucMinor),&wsaData);
	if(0 == nRet) {
		m_bInit = TRUE;
	} else {
		m_bInit = FALSE;
	}

	return nRet;
}

// 设置服务器信息
BOOL CMyUDP::SetServer(char *szIP,int nPort) {
	BOOL bRet = FALSE;
	struct hostent *host = NULL;

	SAFE_CLOSESOCKET(m_socket);
	m_socket = socket(AF_INET,SOCK_DGRAM,0);
	if(INVALID_SOCKET == m_socket) {
		goto END;
	}

	host = gethostbyname(szIP);
	if(NULL == host) {
		goto END;
	}

	m_sockaddr.sin_family = AF_INET;
	m_sockaddr.sin_addr = *((struct in_addr*)host->h_addr_list[0]);
	m_sockaddr.sin_port = htons(nPort);	
	bRet = TRUE;

END:
	if(!bRet) {
		SAFE_CLOSESOCKET(m_socket);
	}
	return bRet;
}

// 发送数据
int CMyUDP::SendTo(char *buf,int nSize) {
	return sendto(m_socket,buf,nSize,0,(SOCKADDR*)&m_sockaddr,sizeof(m_sockaddr));
}

// 接收数据
int CMyUDP::RecvFrom(char *buf,int nSize,DWORD dwMillisecords) {
	int bRet = SOCKET_ERROR;
	DWORD dwFlag = 0;
	int nLen = sizeof(m_sockaddr);

	m_PerIoData.wsaBuf.buf = buf;
	m_PerIoData.wsaBuf.len = nSize;

	if(0 == WSARecvFrom(m_socket,&m_PerIoData.wsaBuf,1,(DWORD*)&bRet,&dwFlag,
		(SOCKADDR*)&m_sockaddr,&nLen,&m_PerIoData.overlapped,CompletionRoutineFunc)) {
		goto END;
	}

	if(SOCKET_ERROR==bRet && WSA_IO_PENDING==WSAGetLastError()) {
		if(WAIT_IO_COMPLETION == WSAWaitForMultipleEvents(1,&m_PerIoData.wsaEvent,FALSE,dwMillisecords,TRUE)) {
			WSAResetEvent(m_PerIoData.wsaEvent);
			bRet = m_PerIoData.cbBytes;
		}
	}
	
END:
	return bRet;
}

// 完成例程回调函数
void CALLBACK CMyUDP::CompletionRoutineFunc(
	DWORD dwError,
	DWORD cbTransferred,
	LPWSAOVERLAPPED lpOverlapped,
	DWORD dwFlags) {
	PER_IO_DATA *pPerIoData = (PER_IO_DATA*)lpOverlapped;

	pPerIoData->cbBytes = cbTransferred;
	WSASetEvent(pPerIoData->wsaEvent);

	return;
}

  再上NTP类源码:


MyNTP.h

#include "MyUDP.h"
#include 
#include 

typedef struct _QWORD {
	DWORD LoDWORD;
	DWORD HiDWORD;
}QWORD,*PQWORD;

typedef struct _NTP_DATA {
	struct {
		// 注:Windows采用的是小端对齐,所以顺序要反过来
		/*
		Mode:指示协议模式。字段长度为3位,取值定义为:
		Mode=0:保留
		Mode=1:对称主动;
		Mode=2:对称被动;
		Mode=3:客户;
		Mode=4:服务器;
		Mode=5:广播;
		Mode=6:保留为NTP控制信息;
		Mode=7:保留为用户定义;
		在单播和多播模式,客户在请求时把这个字段设置为3,服务器在响应时把这个字段设置为4。在广播模式下,服务器把这个字段设置为5。
		*/
		BYTE Mode:3;
		// VN:版本号。字段长度为3位整数,当前版本号为4。
		BYTE VN:3;
		/*
		LI:当前时间闰秒标志。字段长度为2位整数,只在服务器端有效。取值定义为:
		LI=0:无警告;
		LI=1:最后一分钟是61秒;
		LI=2:最后一分钟是59秒;
		LI=3:警告(时钟没有同步)
		*/
		BYTE LI:2;
	} Control;
	/*
	Stratum:指示服务器工作的级别,该字段只在服务器端有效,字段长度为8位整数。取值定义为:
	Stratum=0:故障信息;
	Stratum=1:一级服务器;
	Stratum=2-15:二级服务器;
	Stratum=16-255:保留;
	*/
	BYTE Stratum;
	// Poll Interval:指示数据包的最大时间间隔,以秒为单位,作为2的指数方的指数部分,该字段只在服务器端有效。
	// 字段长度为8位整数,取值范围从4-17,即16秒到131,072秒。
	BYTE Poll;
	// Precision:指示系统时钟的精确性,以秒为单位,作为2的指数方的指数部分,该字段只在服务器端有效。
	// 字段长度为8位符号整数,取值范围从-6到-20。
	char Precision;

	// Root Delay:指示与主时钟参考源的总共往返延迟,以秒为单位,该字段只在服务器端有效。
	// 字段长度为32位浮点数,小数部分在16位以后,取值范围从负几毫秒到正几百毫秒。
	float Root_Delay;
	// Root Dispersion:指示与主时钟参考源的误差,以秒为单位,该字段只在服务器端有效。
	// 字段长度为32位浮点数,小数部分在16位以后,取值范围从零毫秒到正几百毫秒。
	float Root_Dispersion;
	// Reference Identifier:指示时钟参考源的标记,该字段只在服务器端有效。
	// 对于一级服务器,字段长度为4字节ASCII字符串,左对齐不足添零。
	// 对于二级服务器,在IPV4环境下,取值为一级服务器的IP地址,在IPV6环境下,是一级服务器的NSAP地址。
	DWORD Reference_Identifier;

	// Reference Timestamp:指示系统时钟最后一次校准的时间戳,该字段只在服务器端有效,低32位自1900年以来的秒数,高32位微秒数的(2^32/10^6)倍。
	QWORD Reference_Timestamp;
	// Originate Timestamp:指示客户向服务器发起请求的时间戳,低32位自1900年以来的秒数,高32位微秒数的(2^32/10^6)倍。
	QWORD Originate_Timestamp;
	// Receive Timestamp:指示客户请求到达服务器的时间戳,低32位自1900年以来的秒数,高32位微秒数的(2^32/10^6)倍。
	QWORD Receive_Timestamp;
	// Transmit Timestamp:指示服务器向客户回复请求的时间戳,低32位自1900年以来的秒数,高32位微秒数的(2^32/10^6)倍。
	QWORD Transmit_Timestamp;

	// Authenticator(可选):当需要进行SNTP认证时,该字段包含密钥和信息加密码。
	BYTE Authenticator[12];
}NTP_DATA,*PNTP_DATA;

class CMyNTP {
private:
	NTP_DATA m_NtpData;
	CMyUDP m_udp;

public:
	CMyNTP();
	~CMyNTP();

	// 向服务器查询时间
	BOOL QueryTimeInfo(char *szIP,DWORD dwMillisecords);
	// 获取查询时间的秒数
	DWORD GetSenconds();
	// 设置本地时间为网络校准时间
	BOOL SetNetworkTime();
};

MyNTP.cpp

#include "MyNTP.h"

CMyNTP::CMyNTP() {
	memset(&m_NtpData,0,sizeof(m_NtpData));
}

CMyNTP::~CMyNTP() {
}

// 向服务器查询时间
BOOL CMyNTP::QueryTimeInfo(char *szIP,DWORD dwMillisecords) {
	BOOL bRet = FALSE;

	memset(&m_NtpData,0,sizeof(m_NtpData));
	m_NtpData.Control.LI = 0;
	m_NtpData.Control.VN = 3;
	m_NtpData.Control.Mode = 3;

	if(!m_udp.SetServer(szIP,123)) {
		goto END;
	}

	if(SOCKET_ERROR == m_udp.SendTo((char*)&m_NtpData,sizeof(m_NtpData))) {
		goto END;
	}

	if(SOCKET_ERROR == m_udp.RecvFrom((char*)&m_NtpData,sizeof(m_NtpData),dwMillisecords)) {
		goto END;
	}

	if(m_NtpData.Stratum > 0) {
		bRet = TRUE;
	}

END:
	return bRet;
}

// 获取查询时间的秒数
DWORD CMyNTP::GetSenconds() {
	DWORD dwSenconds = 0;

	dwSenconds = ntohl(m_NtpData.Transmit_Timestamp.LoDWORD);
	// 返回是自1900年起,换算成1970年起。
	dwSenconds -= 2208988800;

	return dwSenconds;
}

// 设置本地时间为网络校准时间
BOOL CMyNTP::SetNetworkTime() {
	BOOL bRet = FALSE;
	time_t time = GetSenconds();
	struct tm *pTM = localtime(&time);
	SYSTEMTIME SystemTime = {0};

	if(pTM) {
		SystemTime.wYear = pTM->tm_year + 1900;
		SystemTime.wMonth = pTM->tm_mon + 1;
		SystemTime.wDayOfWeek = pTM->tm_wday;
		SystemTime.wDay = pTM->tm_mday;
		SystemTime.wHour = pTM->tm_hour;
		SystemTime.wMinute = pTM->tm_min;
		SystemTime.wSecond = pTM->tm_sec;
		SystemTime.wMilliseconds = m_NtpData.Transmit_Timestamp.HiDWORD/(pow(2,32)/pow(10,6))/1000;
		SetLocalTime(&SystemTime);

		bRet = TRUE;
	}

	return bRet;
}


你可能感兴趣的:(代码人生)