网络NTP协议时间校对源码
2017-09-03 22:11
1011 查看
从科锐培训中断回来,好几个月都没写过博客,以后像在武汉这样学习的日子大概不会有了。最近三个月换了两份工作,也够折腾,期间有想过重回科锐的念头,种种原因最后放弃了。
写这个NTP校时的程序,起因是家里旧笔记本的BIOS没电了,每次断电重启都要重设时间,于是就决定自己写一个。NTP服务器使用UDP协议和123端口号,查询时间的具体方法及NTP协议数据格式见源码及注释,还是比较简单的。代码中只是简单的读取服务器返回的时间,没有去计算修正网络传输的来回延时,对我来说精度已经足够了,有需求的朋友可以改一下。
废话不多说,先上UDP类源码:
MyUDP.h:
MyUDP.cpp
再上NTP类源码:
MyNTP.h
MyNTP.cpp
写这个NTP校时的程序,起因是家里旧笔记本的BIOS没电了,每次断电重启都要重设时间,于是就决定自己写一个。NTP服务器使用UDP协议和123端口号,查询时间的具体方法及NTP协议数据格式见源码及注释,还是比较简单的。代码中只是简单的读取服务器返回的时间,没有去计算修正网络传输的来回延时,对我来说精度已经足够了,有需求的朋友可以改一下。
废话不多说,先上UDP类源码:
MyUDP.h:
#pragma once #include <winsock2.h> #include <windows.h> #include <tchar.h> #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 <time.h> #include <math.h> 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; }
相关文章推荐
- 同步网络时间的 NTP 协议被发现存在8个漏洞
- NTP-网络时间协议
- 网络时间协议(Network Time Protocal, NTP)学习笔记
- 网络时间协议(NTP)--网络大典
- ntpdate命令_Linux ntpdate 命令用法详解:使用网络计时协议(NTP)设置日期和时间
- 同步网络时间的 NTP 协议被发现存在8个漏洞
- NTP协议网络时间同步 ios中应用 实现秒杀倒计时
- 网络时间协议(NTP)
- 网络时间协议Network Time Protocol(NTP)
- NTP网络时间协议
- 网络时间协议(NTP)的安装
- 网络时间协议简介-----NTP(Network Time Protocol)
- 通过网络NTP协议进行时间同步
- STM32+NTP网络时间协议
- 【RL-TCPnet网络教程】第29章 NTP网络时间协议基础知识
- RHCE 系列(十):在 RHEL/CentOS 7 中设置 NTP(网络时间协议)服务器
- NTP 网络时间协议服务配置说明(Windows)
- NTP(网络时间协议)可以利用ntplib模块。
- Linux的归档及压缩,Linux的cron时间计划任务, NTP网络时间协议 , 查看网络连接
- linux C++通过ntp协议获取网络时间