我正在尝试获取通过 1 根以太网电缆连接的 2 台计算机之间的硬件时间戳中的数据包传输延迟。然而,获得延迟的结果是11.5 micro-seconds
,这比我预期的要高出几纳秒。
设计
我试图获得延迟的方式如下图所示delay = ((t3 - t0) - (t2 - t1)) / 2
client server
| ping |
t0|-------------->|t1
| |
| pong |
t3|<--------------|t2
| |
v v
这里t0/t2
是网卡记录的Linux硬件发送时间,t1/t3
是硬件接收时间戳。有关 linux socket 硬件时间的详细信息可以在这里找到:https ://docs.kernel.org/networking/timestamping.html 。据我了解,t0/t2
网卡将第一个字节传输到线路t1/t3
的时间,是网卡从线路接收第一个字节的时间。因此,我计算的延迟纯粹是传播延迟,应该在几纳秒内。除了证明我的假设之外,我还尝试改变数据包大小(以256/512/1400
字节为单位),结果没有任何变化,都在11.5 microseconds
. 我对那些硬件时间戳和延迟计算的理解是否正确?
执行
我使用的两台电脑是Dell OptiPlex-7090
Ubuntu 20.04,网卡版本是Ethernet Connection (14) I219-LM
.
完整的测试代码可以在这里找到:https ://github.com/ChuanyuXue/Hardware-RTT 。它记录硬件时间戳和软件时间戳。
实用程序.c:
void die(char *s)
{
perror(s);
exit(1);
}
int setup_receiver(int fd, int port)
{
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
die("bind()");
}
int val = SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_RAW_HARDWARE;
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val)) == -1)
{
die("setsockopt() HW receiving");
}
int enable = 1;
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPNS, &enable, sizeof(enable)) < 0)
{
die("setsockopt() SW receiving");
}
return 0;
}
int setup_sender(int fd)
{
int timestamp_flags = SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_RAW_HARDWARE;
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, ×tamp_flags, sizeof(timestamp_flags)) < 0)
{
die("setsockopt() HW sending");
}
int enable = 1;
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPNS, &enable, sizeof(enable)))
{
die("setsockopt() SW receiving");
}
}
int setup_adapter(int fd, char *dev_name)
{
struct hwtstamp_config hwts_config;
struct ifreq ifr;
memset(&hwts_config, 0, sizeof(hwts_config));
hwts_config.tx_type = HWTSTAMP_TX_ON;
hwts_config.rx_filter = HWTSTAMP_FILTER_ALL;
memset(&ifr, 0, sizeof(ifr));
snprintf(ifr.ifr_name, IFNAMSIZ, "%s", dev_name);
ifr.ifr_data = (void *)&hwts_config;
if (ioctl(fd, SIOCSHWTSTAMP, &ifr) == -1)
{
die("ioctl()");
}
}
void send_single(int fd, char *address, int port)
{
/*
Send one message
*/
struct sockaddr_in si_server;
memset(&si_server, 0, sizeof(si_server));
si_server.sin_family = AF_INET;
si_server.sin_port = htons(port);
if (inet_aton(address, &si_server.sin_addr) == 0)
{
die("inet_aton()");
}
char buffer[BUFFER_LEN];
struct iovec iov = (struct iovec){.iov_base = buffer, .iov_len = BUFFER_LEN};
struct msghdr msg = (struct msghdr){.msg_name = &si_server,
.msg_namelen = sizeof si_server,
.msg_iov = &iov,
.msg_iovlen = 1};
ssize_t send_len = sendmsg(fd, &msg, 0);
if (send_len < 0)
{
printf("[!] Error sendmsg()");
}
// -------------- obtain the loopback packet
char data[BUFFER_LEN], control[BUFFER_LEN];
struct iovec entry;
struct sockaddr_in from_addr;
int res;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &entry;
msg.msg_iovlen = 1;
entry.iov_base = data;
entry.iov_len = sizeof(data);
msg.msg_name = (caddr_t)&from_addr;
msg.msg_namelen = sizeof(from_addr);
msg.msg_control = &control;
msg.msg_controllen = sizeof(control);
// wait until get the loopback
while (recvmsg(fd, &msg, MSG_ERRQUEUE) < 0)
{
}
// encode the returned packet
struct cmsghdr *cmsg;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
cmsg = CMSG_NXTHDR(&msg, cmsg))
{
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_TIMESTAMPING)
{
struct timespec *ts =
(struct timespec *)CMSG_DATA(cmsg);
printf("HD-SEND TIMESTAMP %ld.%09ld\n", ts[2].tv_sec, ts[2].tv_nsec);
}
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SO_TIMESTAMPNS)
{
struct timespec *ts =
(struct timespec *)CMSG_DATA(cmsg);
printf("SW-SEND TIMESTAMP %ld.%09ld\n", ts->tv_sec, ts->tv_nsec);
}
}
}
void recv_single(int fd)
{
char data[BUFFER_LEN], ctrl[BUFFER_LEN];
struct msghdr msg;
struct iovec iov;
ssize_t len;
struct cmsghdr *cmsg;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = ctrl;
msg.msg_controllen = sizeof(ctrl);
iov.iov_base = data;
iov.iov_len = sizeof(data);
struct timespec start;
if (recvmsg(fd, &msg, 0) < 0)
{
printf("[!] Error recvmsg()");
}
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
cmsg = CMSG_NXTHDR(&msg, cmsg))
{
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_TIMESTAMPING)
{
struct timespec *ts =
(struct timespec *)CMSG_DATA(cmsg);
printf("HW-RECV TIMESTAMP %ld.%09ld\n", ts[2].tv_sec, ts[2].tv_nsec);
}
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_TIMESTAMPNS)
{
struct timespec *ts =
(struct timespec *)CMSG_DATA(cmsg);
printf("SW-RECV TIMESTAMP %ld.%09ld\n", ts->tv_sec, ts->tv_nsec);
}
}
}
客户端.c
int main(int argc, char *argv[])
{
const char *address = "192.168.0.23";
const int port = 54321;
int fd_in = socket(AF_INET, SOCK_DGRAM, 0);
int fd_out = socket(AF_INET, SOCK_DGRAM, 0);
setup_adapter(fd_in, "eth0");
setup_adapter(fd_out, "eth0");
setup_sender(fd_out);
setup_receiver(fd_in, port);
int count = 0;
while (1)
{
printf("[ ---- Iter-%5d ----------------------------- ]\n", count++);
send_single(fd_out, (char *)address, port);
recv_single(fd_in);
usleep(10000);
}
}
服务器.c
int main(int argc, char *argv[])
{
const char *address = "192.168.0.22";
const int port = 54321;
int fd_in = socket(AF_INET, SOCK_DGRAM, 0);
int fd_out = socket(AF_INET, SOCK_DGRAM, 0);
setup_adapter(fd_in, "eth0");
setup_adapter(fd_out, "eth0");
setup_sender(fd_out);
setup_receiver(fd_in, port);
int count = 0;
while (1)
{
printf("[ ---- Iter-%5d ----------------------------- ]\n", count++);
recv_single(fd_in);
usleep(10000);
send_single(fd_out, (char *)address, port);
}
}
从结果中,我可以看到硬件时间戳比软件时间戳更稳定,并且抖动非常低(对不起,我的帐户不允许嵌入图片到问题中):
https://github.com/ChuanyuXue/Hardware-RTT/blob/main/0301_exp_hwtime/01.png
https://github.com/ChuanyuXue/Hardware-RTT/blob/main/0301_exp_hwtime/02.png
https://github.com/ChuanyuXue/Hardware-RTT/blob/main/0301_exp_hwtime/03.png
https://github.com/ChuanyuXue/Hardware-RTT/blob/main/0301_exp_hwtime/04.png
https://github.com/ChuanyuXue/Hardware-RTT/blob/main/0301_exp_hwtime/06.png
以上结果让我觉得我得到的时间戳是正确的,因为硬件延迟具有较小的延迟和非常低的抖动,但是我在计算时忽略了一些问题delay = ((t3 - t0) - (t2 - t1)) / 2
。有没有人可以让我知道原因或在其他环境中测试我的代码?