背景
机器每次机启后时间就会出现异常,因为机器无法访问外网,只能访问局域网的ntp服务,所以需要保证局域网内部有ntp服务,如何安装ntp服务,参考Ubuntu20.04 Ntp服务安装及验证。
网络时间协议Network Time Protocol(NTP) 是一种确保时钟保持准确的方法。如果可以访问互联网,只需安装ntp的客户端软件到互联网上的公共ntp服务器自动修正时间即可
一、系统时间和硬件时间
Linux在默认情况下,系统时间和硬件时间并不会自动同步。而是以异步的方式运行,互不干扰。其中硬件时间的运行,是靠Bios电池来维持,而系统时间,是用CPU 时钟来维持的。
在系统开机的时候,会自动从Bios中取得硬件时间,设置为系统时间。
1.1 date命令
用来查看和设置系统时间
date #查看系统当前时间 sudo date -s "2023-03-18 11:16:10" #修改系统时间为 "xxxx-xx-xx xx:xx:xx" =============================================================================== nvidia@nvidia-desktop:~$ date Вт мар 18 11:16:27 +08 2023 nvidia@nvidia-desktop:~$ nvidia@nvidia-desktop:~$ nvidia@nvidia-desktop:~$ sudo date -s "2023-03-18 11:16:10" [sudo] password for nvidia: Вт мар 18 11:16:10 +08 2023 nvidia@nvidia-desktop:~$
硬件时间的设置,可以用hwclock
1.2 hwclock 命令
查看当前硬件时间
注意:hwclock 所有命令需要使用root 权限
nvidia@nvidia-desktop:~$ hwclock hwclock: Cannot access the Hardware Clock via any known method. hwclock: Use the --debug option to see the details of our search for an access method. nvidia@nvidia-desktop:~$ nvidia@nvidia-desktop:~$ nvidia@nvidia-desktop:~$ sudo hwclock 2023-03-21 11:18:49.607690+0800 nvidia@nvidia-desktop:~$
将系统时间同步到硬件时间
hwclock -w
将硬件时间同步到系统时间
hwclock -s
二、不同机器间时间同步
为了避免主机时间因为长期运作下所导致的时间偏差,进行时间同步(synchronize)的工作是非常必要的。Linux系统下,一般使用ntp服务器来同步不同机器的时间。一台机器,可以同时是ntp服务器和ntp客户机。
2.1 ntpdate命令实现
ntpdate 安装:
yum install ntpdate -y # Centos系统 ====================================== sudo apt install ntpdate # Ubuntu系统
时间同步
sudo ntpdate -u cn.pool.ntp.org 18 Mar 18:25:22 ntpdate[18673]: adjust time server 84.16.73.33 offset 0.015941 sec
使用ntpdate 只是强制将系统时间设置为ntp服务器时间,如果cpu tick有问题,时间还是会不准。所以,一般配合cron命令,来进行定期同步设置。比如,在crontab中添加:
sudo crontab -e 0 12 * * * * /usr/sbin/ntpdate 192.168.10.110
上述命令的意思是:每天的12点整,从192.168.10.110 ntp服务器同步一次时间(前提是 192.168.10.110有ntp服务)。
2.2 Ntp客户端代码实现
本质上还是创建socket连接去获取ntp服务的时间与本地时间比较,不一致修改本机时间即可。
NtpClient.h
// // Created by lwang on 2023-03-18. // #ifndef NTP_CLIENT_H #define NTP_CLIENT_H #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <iostream> #include <unistd.h> #include <sys/select.h> #include <sys/time.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netdb.h> #include <errno.h> #include <endian.h> #include <map> #include <string> #include <mutex> using namespace std; #define NTP_LI 0 #define NTP_VERSION_NUM 3 #define NTP_MODE_CLIENT 3 #define NTP_MODE_SERVER 4 #define NTP_STRATUM 0 #define NTP_POLL 4 #define NTP_PRECISION -6 #define NTP_MIN_LEN 48 #define NTP_SERVER_PORT 123 #define NTP_SERVER_ADDR "119.28.183.184" #define TIMEOUT 2 #define BUFSIZE 1500 #define JAN_1970 0x83aa7e80 #define NTP_CONV_FRAC32(x) (uint64_t)((x) * ((uint64_t)1 << 32)) #define NTP_REVE_FRAC32(x) ((double)((double)(x) / ((uint64_t)1 << 32))) #define NTP_CONV_FRAC16(x) (uint32_t)((x) * ((uint32_t)1 << 16)) #define NTP_REVE_FRAC16(x) ((double)((double)(x) / ((uint32_t)1 << 16))) #define USEC2FRAC(x) ((uint32_t)NTP_CONV_FRAC32((x) / 1000000.0)) #define FRAC2USEC(x) ((uint32_t)NTP_REVE_FRAC32((x)*1000000.0)) #define NTP_LFIXED2DOUBLE(x) ((double)(ntohl(((struct l_fixedpt *)(x))->intpart) - JAN_1970 + FRAC2USEC(ntohl(((struct l_fixedpt *)(x))->fracpart)) / 1000000.0)) struct s_fixedpt { uint16_t intpart; uint16_t fracpart; }; struct l_fixedpt { uint32_t intpart; uint32_t fracpart; }; struct ntphdr { #if __BYTE_ORDER == __BID_ENDIAN unsigned int ntp_li : 2; unsigned int ntp_vn : 3; unsigned int ntp_mode : 3; #endif #if __BYTE_ORDER == __LITTLE_ENDIAN unsigned int ntp_mode : 3; unsigned int ntp_vn : 3; unsigned int ntp_li : 2; #endif uint8_t ntp_stratum; uint8_t ntp_poll; int8_t ntp_precision; struct s_fixedpt ntp_rtdelay; struct s_fixedpt ntp_rtdispersion; uint32_t ntp_refid; struct l_fixedpt ntp_refts; struct l_fixedpt ntp_orits; struct l_fixedpt ntp_recvts; struct l_fixedpt ntp_transts; }; class NtpClient { public: NtpClient(); virtual ~NtpClient(); void GetNtpTime(std::string &ntpTime); in_addr_t HostTransfer(const char *host); int PaddingNtpPackage(void *buf, size_t *size); double GetOffset(const struct ntphdr *ntp, const struct timeval *recvtv); private: int m_sockfd; }; #endif /* NTP_CLIENT_H */
NtpClient.cpp
// // Created by lwang on 2023-03-18. // #include "NtpClient.h" NtpClient::NtpClient() { } NtpClient::~NtpClient() {} in_addr_t NtpClient::HostTransfer(const char *host) { in_addr_t saddr; struct hostent *hostent; if ((saddr = inet_addr(host)) == INADDR_NONE) { if ((hostent = gethostbyname(host)) == NULL){ return INADDR_NONE; } memmove(&saddr, hostent->h_addr, hostent->h_length); } return saddr; } int NtpClient::PaddingNtpPackage(void *buf, size_t *size) // 构建并发送NTP请求报文 { if (!size) return -1; struct ntphdr *ntp; struct timeval tv; memset(buf, 0, BUFSIZE); ntp = (struct ntphdr *)buf; ntp->ntp_li = NTP_LI; ntp->ntp_vn = NTP_VERSION_NUM; ntp->ntp_mode = NTP_MODE_CLIENT; ntp->ntp_stratum = NTP_STRATUM; ntp->ntp_poll = NTP_POLL; ntp->ntp_precision = NTP_PRECISION; gettimeofday(&tv, NULL); // 把目前的时间用tv 结构体返回 ntp->ntp_transts.intpart = htonl(tv.tv_sec + JAN_1970); ntp->ntp_transts.fracpart = htonl(USEC2FRAC(tv.tv_usec)); *size = NTP_MIN_LEN; return 0; } double NtpClient::GetOffset(const struct ntphdr *ntp, const struct timeval *recvtv) // 偏移量 { double t1, t2, t3, t4; t1 = NTP_LFIXED2DOUBLE(&ntp->ntp_orits); t2 = NTP_LFIXED2DOUBLE(&ntp->ntp_recvts); t3 = NTP_LFIXED2DOUBLE(&ntp->ntp_transts); t4 = recvtv->tv_sec + recvtv->tv_usec / 1000000.0; return ((t2 - t1) + (t3 - t4)) / 2; } void NtpClient::GetNtpTime(std::string &ntpTime) { char buffer[64] = {0}; char cmd[128] = {0}; tm *local; char buf[BUFSIZE]; size_t nbytes; int maxfd1; struct sockaddr_in servaddr; fd_set readfds; struct timeval timeout, recvtv, tv; double offset; servaddr.sin_family = AF_INET; servaddr.sin_port = htons(NTP_SERVER_PORT); servaddr.sin_addr.s_addr = HostTransfer(NTP_SERVER_ADDR); if ((m_sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){ perror("socket error"); return ; } if (connect(m_sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr)) != 0){ perror("connect error"); return ; } nbytes = BUFSIZE; if (PaddingNtpPackage(buf, &nbytes) != 0){ fprintf(stderr, "construct ntp request error \n"); exit(-1); } send(m_sockfd, buf, nbytes, 0); FD_ZERO(&readfds); FD_SET(m_sockfd, &readfds); maxfd1 = m_sockfd + 1; timeout.tv_sec = TIMEOUT; timeout.tv_usec = 0; if (select(maxfd1, &readfds, NULL, NULL, &timeout) > 0){ if (FD_ISSET(m_sockfd, &readfds)){ if ((nbytes = recv(m_sockfd, buf, BUFSIZE, 0)) < 0){ perror("recv error"); exit(-1); } // 计算C/S时间偏移量 gettimeofday(&recvtv, NULL); offset = GetOffset((struct ntphdr *)buf, &recvtv); gettimeofday(&tv, NULL); tv.tv_sec += (int)offset; tv.tv_usec += offset - (int)offset; local = localtime((time_t *)&tv.tv_sec); strftime(buffer, 64, "%Y-%m-%d %H:%M:%S", local); ntpTime = std::string(buffer); } } return ; }
main.cpp
#include "NtpClient.h" int main() { std::string ntpTime = ""; char curBuf[64] = {0}; struct timeval cur; tm *local; NtpClient client; client.GetNtpTime(ntpTime); cout << "ntpTime: " << ntpTime << endl; gettimeofday(&cur, NULL); local = localtime((time_t *)&cur.tv_sec); strftime(curBuf, 64, "%Y-%m-%d %H:%M:%S", local); std::string curTime = std::string(curBuf); cout << "curTime: " << curTime << endl; if (curTime != ntpTime){ cout << "start time calibrate!" << endl; std::string cmd = "sudo date -s \"" + ntpTime + "\""; system(cmd.c_str()); cout << "cmd: " << cmd << endl; }else{ cout << "time seem" << endl; } return 0; }