13-Roundtrip代码分析

背景

仿照NTP原理,测量两台主机的时钟差,但是将原本T2和T3的合作一个时间点(由于T2和T3之间的间隔很小,对于非严格测试可以忽略)

image-20230212110245465

代码分析(UDP, two threads)

  • 消息格式

16B,T1和T2。是主机字节序。发送时客户端将T1填上,服务端接收后,将T2填上。

 struct Message
 {
   int64_t request; // T1,客户端填上
   int64_t response; // T2,服务端填上
 } __attribute__ ((__packed__));

 

  • 服务端

 void runServer(bool ipv6)
 {
   Socket sock(Socket::createUDP(ipv6 ? AF_INET6 : AF_INET));
   sock.bindOrDie(InetAddress(g_port, ipv6));
 
   while (true)
  {
     Message message = { 0, 0 };
 
     struct sockaddr_storage peerAddr;
     bzero(&peerAddr, sizeof peerAddr);
     socklen_t addrLen = sizeof peerAddr;
     struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&peerAddr);
 
     ssize_t nr = ::recvfrom(sock.fd(), &message, sizeof message, 0, addr, &addrLen); // 接收信息
     if (nr == sizeof message)
    {
       message.response = now();  // 填上T2
       ssize_t nw = ::sendto(sock.fd(), &message, sizeof message, 0, addr, addrLen);  // 发送信息
       if (nw < 0)
      {
         perror("send Message");
      }
       else if (nw != sizeof message)
      {
         printf("sent message of %zd bytes, expect %zd bytes.\n", nw, sizeof message);
      }
    }
     else if (nr < 0)
    {
       perror("recv Message");
    }
     else
    {
       printf("received message of %zd bytes, expect %zd bytes.\n", nr, sizeof message);
    }
  }
 }

 

  • 客户端:不能像服务端一样用一个线程,先调用sendto后调用recvfrom,因为sendto可能会丢包,导致recvfrom一直堵塞。所以由两个线程组成,一个负责不断发request,一个辅助不断收response。

 void runClient(const char* server_hostname)
 {
   InetAddress serverAddr;
   if (!InetAddress::resolve(server_hostname, g_port, &serverAddr))
  {
     printf("Unable to resolve %s\n", server_hostname);
     return;
  }
 
   Socket sock(Socket::createUDP(serverAddr.family()));
 
   if (sock.connect(serverAddr) != 0)
  {
     perror("connect to server");
     return;
  }
 
   // c++11线程,线程入口函数为lambda表达式,每个200ms就发送一个request  
   std::thread thr([&sock] () {
     while (true)
    {
       Message message = { 0, 0 };
       message.request = now();
       int nw = sock.send(&message, sizeof message);
       if (nw < 0)
      {
         perror("send Message");
      }
       else if (nw != sizeof message)
      {
         printf("sent message of %d bytes, expect %zd bytes.\n", nw, sizeof message);
      }
 
      ::usleep(200*1000);
    }
  });
 
   while (true)
  {
     Message message = { 0, 0 };
     int nr = sock.recv(&message, sizeof message);
     if (nr == sizeof message)
    {
       int64_t back = now();
       // 计算时间差
       int64_t mine = (back + message.request) / 2;
       printf("now %jd, round trip time %jd us, clock error %jd us\n",
              back, back - message.request, message.response - mine);
    }
     else if (nr < 0)
    {
       perror("recv Message");
    }
     else
    {
       printf("received message of %d bytes, expect %zd bytes.\n", nr, sizeof message);
    }
  }
 }

 

代码出处

 

测试步骤

  • 输入命令ntptimentpq -qn查看本机时钟信息以及NTP校验。

image-20230212111451664

 

  • 以atom机为服务端,以e6400为客户端。客户端会依次打印当前时间戳(秒)、往返延迟(微妙)、时钟差(微妙)

image-20230212111614708

 

扩展

clockdiff命令 – 检测两台linux主机的时间差 – Linux命令大全(手册) (linuxcool.com)

posted @ 2023-04-29 15:18  DavidJIAN  阅读(19)  评论(0)    收藏  举报