网络时间同步(NTP)原理
网络时钟同步的工作过程如下:
-
Device A发送一个NTP报文给Device B,该报文带有它离开Device A时的时间戳,该时间戳为10:00:00am(T1)。
-
当此NTP报文到达Device B时,Device B加上自己的时间戳,该时间戳为11:00:01am(T2)。
-
当此NTP报文离开Device B时,Device B再加上自己的时间戳,该时间戳为11:00:02am(T3)。
-
当Device A接收到该响应报文时,Device A的本地时间为10:00:03am(T4)。
NTP报文的往返时延Delay=(T4-T1)-(T3-T2)
Device A相对Device B的时间差offset=((T2-T1)+(T3-T4))/2
RounTrip代码分析
RoundTrip就是按照NTP时间同步的原理,实现一个测量两台机器之间时间误差的程序,在实现中服务端将收到数据的时间与发送应答的时间抽象为一个时间点,即忽略server端处理数据的时间误差。
代码位置:
- UDP with muduo:muduo- master/examples/roundtrip/roundtrip_udp.cc
- TCP with muduo:muduo- master/examples/roundtrip/roundtrip.cc
roundtrip_udp.cc服务端
c
void serverReadCallback(int sockfd, muduo::Timestamp receiveTime)
{
int64_t message[2];
struct sockaddr peerAddr;
bzero(&peerAddr, sizeof peerAddr);
socklen_t addrLen = sizeof peerAddr;
ssize_t nr = ::recvfrom(sockfd, message, sizeof message, 0, &peerAddr, &addrLen);
char addrStr[32];
sockets::toIpPort(addrStr, sizeof addrStr, *reinterpret_cast<struct sockaddr_in*>(&peerAddr));
LOG_DEBUG << "received " << nr << " bytes from " << addrStr;
if (nr < 0)
{
LOG_SYSERR << "::recvfrom";
}
else if (implicit_cast<size_t>(nr) == frameLen)
{
// 记录一个时间戳 并返回给客户端
message[1] = receiveTime.microSecondsSinceEpoch();
ssize_t nw = ::sendto(sockfd, message, sizeof message, 0, &peerAddr, addrLen);
if (nw < 0)
{
LOG_SYSERR << "::sendto";
}
else if (implicit_cast<size_t>(nw) != frameLen)
{
LOG_ERROR << "Expect " << frameLen << " bytes, wrote " << nw << " bytes.";
}
}
else
{
LOG_ERROR << "Expect " << frameLen << " bytes, received " << nr << " bytes.";
}
}
roundtrip_udp.cc客户端
c
void sendMyTime(int sockfd)
{
int64_t message[2] = { 0, 0 };
// 发送一个当下的时间戳
message[0] = Timestamp::now().microSecondsSinceEpoch();
ssize_t nw = sockets::write(sockfd, message, sizeof message);
if (nw < 0)
{
LOG_SYSERR << "::write";
}
else if (implicit_cast<size_t>(nw) != frameLen)
{
LOG_ERROR << "Expect " << frameLen << " bytes, wrote " << nw << " bytes.";
}
}
void clientReadCallback(int sockfd, muduo::Timestamp receiveTime)
{
int64_t message[2];
ssize_t nr = sockets::read(sockfd, message, sizeof message);
if (nr < 0)
{
LOG_SYSERR << "::read";
}
else if (implicit_cast<size_t>(nr) == frameLen)
{
int64_t send = message[0];
int64_t their = message[1];
int64_t back = receiveTime.microSecondsSinceEpoch();
int64_t mine = (back+send)/2;
LOG_INFO << "round trip " << back - send // 往返时间
<< " clock error " << their - mine; // 服务端相对客户端的时钟差
}
else
{
LOG_ERROR << "Expect " << frameLen << " bytes, received " << nr << " bytes.";
}
}
roundtrip.cc Tcp的代码与上面类似
测试
同一机器下(127.0.0.1)
udp
shell
20240506 13:14:09.259012Z 18477 INFO round trip 707 clock error 66 - roundtrip_udp.cc:93
20240506 13:14:09.461553Z 18477 INFO round trip 828 clock error 66 - roundtrip_udp.cc:93
20240506 13:14:09.662840Z 18477 INFO round trip 952 clock error 57 - roundtrip_udp.cc:93
20240506 13:14:09.863861Z 18477 INFO round trip 755 clock error 59 - roundtrip_udp.cc:93
20240506 13:14:10.064963Z 18477 INFO round trip 855 clock error 71 - roundtrip_udp.cc:93
20240506 13:14:10.267029Z 18477 INFO round trip 966 clock error -23 - roundtrip_udp.cc:93
20240506 13:14:10.470978Z 18477 INFO round trip 829 clock error 40 - roundtrip_udp.cc:93
20240506 13:14:10.671284Z 18477 INFO round trip 841 clock error 64 - roundtrip_udp.cc:93
20240506 13:14:10.872879Z 18477 INFO round trip 951 clock error 97 - roundtrip_udp.cc:93
20240506 13:14:11.074060Z 18477 INFO round trip 716 clock error 66 - roundtrip_udp.cc:93
20240506 13:14:11.275834Z 18477 INFO round trip 873 clock error 38 - roundtrip_udp.cc:93
20240506 13:14:11.477013Z 18477 INFO round trip 897 clock error 102 - roundtrip_udp.cc:93
20240506 13:14:11.679080Z 18477 INFO round trip 741 clock error 70 - roundtrip_udp.cc:9
tcp
shell
20240506 13:14:13.321049Z 11109 INFO round trip 1108 clock error -97 - roundtrip.cc:82
20240506 13:14:13.531021Z 11109 INFO round trip 1079 clock error -79 - roundtrip.cc:82
20240506 13:14:13.740006Z 11109 INFO round trip 1299 clock error -46 - roundtrip.cc:82
20240506 13:14:13.943726Z 11109 INFO round trip 1104 clock error -89 - roundtrip.cc:82
20240506 13:14:14.148380Z 11109 INFO round trip 1158 clock error -91 - roundtrip.cc:82
20240506 13:14:14.350723Z 11109 INFO round trip 1295 clock error 89 - roundtrip.cc:82
20240506 13:14:14.551261Z 11109 INFO round trip 1245 clock error -113 - roundtrip.cc:82
20240506 13:14:14.758256Z 11109 INFO round trip 1744 clock error -351 - roundtrip.cc:82
20240506 13:14:14.962002Z 11109 INFO round trip 1276 clock error -5 - roundtrip.cc:82
20240506 13:14:15.169267Z 11109 INFO round trip 955 clock error -46 - roundtrip.cc:82
20240506 13:14:15.374836Z 11109 INFO round trip 1113 clock error -108 - roundtrip.cc:82
20240506 13:14:15.579069Z 11109 INFO round trip 1125 clock error -61 - roundtrip.cc:82
20240506 13:14:15.780133Z 11109 INFO round trip 1013 clock error -63 - roundtrip.cc:82
round trip指代的是往返时间,clock error指代的是服务端相对客户端的时钟差,单位都是微秒。
总体相比,tcp所用的往返时间还是比udp的往返时间要长,而在同一机器下,理想状态下,时钟差应该为0才对,但是两种方式都各自计算出了不怎么接近0的数值。