手把手写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]);
}
}
参考
- https://blog.csdn.net/Solstice/article/details/6335082
- 《Linux多线程服务端编程》
- 《Linux高性能服务端器编程》
以上是关于手把手写C++服务器(14):基于UDP测量两台机器之间的网络延迟的主要内容,如果未能解决你的问题,请参考以下文章
手把手写C++服务器(30):手撕代码——基于TCP/IP的抛弃服务discard
手把手写C++服务器(22):Linux socket网络编程进阶第一弹