[C++] 实现NTP协议
代码仓库和详细信息在: https://gitee.com/winter_reisender/ez-ntp
基本就是发送数据包-获取数据包-提取数据-计算时间偏差-更新系统时间。
SetSystemTime需要管理员权限。
// 许可: AGPL-3.0
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#pragma comment(lib, "ws2_32.lib") //加载 ws2_32.dll // 用于msvc
constexpr auto NTP_TIMESTAMP_DELTA = 2208988800ull;
/*
NTP时间戳的起点为: 1900/1/1
C语言标准/UNIX时间戳的起点为: 1970/1/1 统一使用
Windows API时间戳起点为: 1601/1/1
*/
using namespace std;
#define dbg(x) cout << #x << " = " << x << endl;
#define show_line() cout << __FILE__ << ":" << __LINE__ << endl;
/*
+-----------+------------+-----------------------+
| Name | Formula | Description |
+-----------+------------+-----------------------+
| leap | leap | leap indicator (LI) |
| version | version | version number (VN) |
| mode | mode | mode |
| stratum | stratum | stratum |
| poll | poll | poll exponent |
| precision | rho | precision exponent |
| rootdelay | delta_r | root delay |
| rootdisp | epsilon_r | root dispersion |
| refid | refid | reference ID |
| reftime | reftime | reference timestamp |
| org | T1 | origin timestamp |
| rec | T2 | receive timestamp |
| xmt | T3 | transmit timestamp |
| dst | T4 | destination timestamp |
| keyid | keyid | key ID |
| dgst | dgst | message digest |
+-----------+------------+-----------------------+
*/
typedef struct {
uint8_t leap_version_mode;
uint8_t stratum;
uint8_t poll;
uint8_t precision;
uint32_t rootDelay;
uint32_t rootDispersion;
uint32_t refId;
uint32_t refTimeSec;
uint32_t refTimeFrac;
uint32_t orgSec;
uint32_t orgFrac;
uint32_t recSec;
uint32_t recFrac;
uint32_t xmtSec;
uint32_t xmtFrac;
} NtpPacket;
inline int64_t calculateTimeOffset(int64_t c1, int64_t s2, int64_t s3,
int64_t c4) {
return (s2 + s3 - c1 - c4) / 2;
}
namespace Utils {
inline uint64_t timbToMilsec(timeb time) {
return time.time * 1000ull + time.millitm;
}
inline timeb ntp2timeb(uint32_t sec, uint32_t frac) {
return timeb{(time_t)(sec - NTP_TIMESTAMP_DELTA),
(unsigned short)(frac * 1000 / MAXUINT32)};
}
inline void printTimeB(const timeb &time) {
if (ctime(&time.time) == NULL) {
cout << "Wrong time! " << time.time << " " << time.millitm << endl;
throw exception();
}
string timeSecString = ctime(&time.time);
cout << timeSecString.substr(0, timeSecString.length() - 1)
<< ", Milsec:" << time.millitm << endl;
}
string timebToString(const timeb time) {
tm *plocal = localtime(&time.time);
assert(plocal);
char str[256] = "";
sprintf(str, "%4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d", plocal->tm_year + 1900,
plocal->tm_mon + 1, plocal->tm_mday, plocal->tm_hour, plocal->tm_min,
plocal->tm_sec);
cout << str << endl;
string result = str;
return result;
}
}; // namespace Utils
class Config {
public:
string ntpServerAddress;
};
class EzNtp {
private:
SOCKET sock;
sockaddr_in socketAddress;
public:
string ntpServerIP = "203.107.6.88";
int ntpServerPort = 123;
void initSocket() {
WSADATA wsaData;
assert(WSAStartup(MAKEWORD(2, 2), &wsaData) == 0);
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
assert(sock);
memset(&socketAddress, 0, sizeof(socketAddress));
socketAddress.sin_family = AF_INET;
socketAddress.sin_addr.s_addr = inet_addr(ntpServerIP.c_str());
socketAddress.sin_port = htons(ntpServerPort);
}
// milsecs
int64_t getNtpOffset() {
NtpPacket packet = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
memset(&packet, 0, sizeof(NtpPacket));
packet.leap_version_mode = 0x1b;
assert(connect(sock, (SOCKADDR *)&socketAddress, sizeof(SOCKADDR)) !=
SOCKET_ERROR);
// --------时间敏感区开始-------
timeb org;
ftime(&org);
assert(send(sock, (const char *)&packet, sizeof(packet), 0) !=
SOCKET_ERROR);
assert(recv(sock, (char *)&packet, sizeof(packet), 0) != SOCKET_ERROR);
timeb dst;
ftime(&dst);
//---------时间敏感区结束------
//转换大小端
auto xmtSec = ntohl(packet.xmtSec);
auto xmtFrac = ntohl(packet.xmtFrac);
auto recSec = ntohl(packet.recSec);
auto recFrac = ntohl(packet.recFrac);
//提取时间
timeb xmt = Utils::ntp2timeb(xmtSec, xmtFrac);
timeb rec = Utils::ntp2timeb(recSec, recFrac);
return calculateTimeOffset(
Utils::timbToMilsec(org), Utils::timbToMilsec(rec),
Utils::timbToMilsec(xmt), Utils::timbToMilsec(dst));
}
timeb getNtpTime() {
auto offset = getNtpOffset();
dbg(offset);
time_t offsetSec = offset / 1000;
short int offsetMilsec = offset % 1000;
timeb localTime;
//---时间敏感区开始---
ftime(&localTime);
localTime.time += offsetSec;
localTime.millitm += offsetMilsec;
localTime.time += localTime.millitm / 1000;
localTime.millitm %= 1000;
return localTime;
}
auto syncTime() { return applyOffset(getNtpOffset()); }
// retunt 0 if success or Win32 Error Code
static DWORD applyOffset(const int64_t offset) {
time_t offsetSec = offset / 1000;
short int offsetMilsec = offset % 1000;
timeb localTime;
//---时间敏感区开始---
ftime(&localTime);
localTime.time += offsetSec;
localTime.millitm += offsetMilsec;
localTime.time += localTime.millitm / 1000;
localTime.millitm %= 1000;
tm t = *localtime(&localTime.time);
SYSTEMTIME newSysTime;
newSysTime.wYear = 1900 + t.tm_year, newSysTime.wMonth = 1 + t.tm_mon,
newSysTime.wDayOfWeek = t.tm_wday, newSysTime.wDay = t.tm_mday,
newSysTime.wHour = t.tm_hour, newSysTime.wMinute = t.tm_min,
newSysTime.wSecond = t.tm_sec,
newSysTime.wMilliseconds = (int)localTime.millitm;
LPSYSTEMTIME pNewSysTime = &newSysTime;
bool isSetSysTimeSuccess = SetLocalTime(&newSysTime);
//------时间敏感区结束--------
return isSetSysTimeSuccess ? 0 : GetLastError();
}
void closeSocket() {
closesocket(sock);
WSACleanup();
}
};
int main() {
auto ezNtp = EzNtp();
ezNtp.initSocket();
Utils::printTimeB(ezNtp.getNtpTime());
DWORD result = ezNtp.syncTime();
cout << ((result == 0) ? "Success" : "Error") << " : " << result << endl;
return 0;
}