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