手把手写C++服务器(14):基于UDP测量两台机器之间的网络延迟

Posted 沉迷单车的追风少年

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手写C++服务器(14):基于UDP测量两台机器之间的网络延迟相关的知识,希望对你有一定的参考价值。

前言:网络延迟是网络编程中不可避免的话题,特别是直播、聊天等实时性要求高的应用场景,对网络延迟特别敏感。常用的测量网络延迟工具有基于TCP的qperf等,但是手写一个测量网络延迟的工具可以更好地帮助我们理解UDP等知识。

目录

原理

代码逻辑

运行效果

源代码

参考

原理

如图所示,客户端发起UDP连接请求,发送message,并记录当前时间T1,服务端收到message后,记录当前时间T1,并把这个message发送回客户端,客户端收到message后记录收到的时间T3.根据T1、T2、T3即可计算网络延迟。

如果没有任何延迟的话,(T1+T3)/2应该和T2相等,但是存在延迟后,T2-(T1+T3)/2就是是网络延迟。

代码逻辑

1、首先注意设计的消息格式,由于UDP是不可靠的,我们需要保证发送的消息和收到的消息是一致的,这里用了判断消息长度。但是可能在两台机器上测量的时候,由于机器的不同,字节长可能不一样,所以程序在编译的时候就要用静态断言判断是否一致,具体可以参考我的上一篇文章:手把手写C++服务器(13):C++11新特性之静态断言static_assert

2、运行的时候要选择服务器已经开放的端口,否则会一直等待,空转。如何查看可以参考: 手把手写C++服务器(12):TCP自连接原理、Python示例、解决方案

3、客户端要单独开一个发送线程,主线程一定要一直死循环等待接收。这里没有做其他的异常,比如中间有大量message丢失,或者违背了先来后到的原则等等,此时由于我们没有做其他异常处理,此处会有丢包。但是我们只要测出已知的时间差就行,丢包就让他丢吧。

4、没有两台机器的话,可以自己和自己测试,只需要指定地址127.0.0.1或者localhost即可。

运行效果

客户端:
./roundtrip_udp 127.0.0.1
服务端
./roundtrip_udp -s

源代码

在chenshuo的代码上加了写注释,其实去除异常处理等非核心代码,核心代码也就三十行左右,比较容易懂。

#include "InetAddress.h"
#include "Socket.h"

#include <thread>

#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>

// 指定端口
const int g_port = 22;

// 传输的消息
struct Message
{
  int64_t request;
  int64_t response;
} __attribute__ ((__packed__));

// 静态断言检查,如果不是16字节,则在编译期间抛出异常
static_assert(sizeof(Message) == 16, "Message size should be 16 bytes");

// 获取当前时间
int64_t now()
{
  struct timeval tv = { 0, 0 };
  gettimeofday(&tv, NULL);
  return tv.tv_sec * int64_t(1000000) + tv.tv_usec;
}
// 服务端
void runServer()
{
  // UDP
  Socket sock(Socket::createUDP());
  // bind端口
  sock.bindOrDie(InetAddress(g_port));

  while (true)
  {
    // 创建要发送的信息
    Message message = { 0, 0 };

    struct sockaddr peerAddr;
    bzero(&peerAddr, sizeof peerAddr);
    socklen_t addrLen = sizeof peerAddr;
    // 接收服务端发送回的信息
    ssize_t nr = ::recvfrom(sock.fd(), &message, sizeof message, 0, &peerAddr, &addrLen);
    // 判断发送回的信息是否和发出去的信息一致
    if (nr == sizeof message)
    {
      // T2
      message.response = now();
      // 再将消息发送回客户端
      ssize_t nw = ::sendto(sock.fd(), &message, sizeof message, 0, &peerAddr, 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);
    }
  }
}

// 客户端
void runClient(const char* server_hostname)
{
  // UDP
  Socket sock(Socket::createUDP());
  // bind端口
  InetAddress serverAddr(g_port);
  // 建立连接失败
  if (!InetAddress::resolve(server_hostname, &serverAddr))
  {
    printf("Unable to resolve %s\\n", server_hostname);
    return;
  }
  if (sock.connect(serverAddr) != 0)
  {
    perror("connect to server");
    return;
  }

  std::thread thr([&sock] () {
    while (true)
    {
      Message message = { 0, 0 };
      // T1
      message.request = now();
      int nw = sock.write(&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.read(&message, sizeof message);
    if (nr == sizeof message)
    {
      // T3
      int64_t back = now();
      // (T1+T3)/2
      int64_t mine = (back + message.request) / 2;
      printf("now %jd round trip %jd clock error %jd\\n",
             back, back - message.request, message.response - mine);
    }
    else if (nr < 0)
    {
      perror("send Message");
    }
    else
    {
      printf("received message of %d bytes, expect %zd bytes.\\n", nr, sizeof message);
    }
  }
}

int main(int argc, const char* argv[])
{
  if (argc < 2) {
    printf("Usage:\\nServer: %s -s\\nClient: %s server_hostname\\n", argv[0], argv[0]);
    return 0;
  }
  
  // 服务端
  if (strcmp(argv[1], "-s") == 0) {
    runServer();
  } else {
    runClient(argv[1]);
  }
}

参考

以上是关于手把手写C++服务器(14):基于UDP测量两台机器之间的网络延迟的主要内容,如果未能解决你的问题,请参考以下文章

手把手写C++服务器(30):手撕代码——基于TCP/IP的抛弃服务discard

手把手写C++服务器(22):Linux socket网络编程进阶第一弹

手把手写C++服务器(22):Linux socket网络编程进阶第一弹

手把手写C++服务器(15):网络编程入门第一个TCP项目

手把手写C++服务器:专栏文章-汇总导航更新中

手把手写C++服务器(27):五大文件描述符零拷贝控制总结