手把手写C++服务器(11):手撕网络带宽测试工具TTCP
Posted 沉迷单车的追风少年
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手写C++服务器(11):手撕网络带宽测试工具TTCP相关的知识,希望对你有一定的参考价值。
前言:TTCP诞生于1984年,是Unix很流行的网络带宽测试工具,并从中衍生出了著名的网络测试工具Ipref。TTCP手撕的源代码量比较小,是网络编程入门的最佳练习demo。本篇文章参考陈硕的TTCP,进行学习分析。
目录
1、program_options控制命令行输入
具体可以看我之前总结的博客:https://xduwq.blog.csdn.net/article/details/117486239
TTCP里提供了发送模式、接收模式、端口设置、长度设置等,需要在命令行指定。
2、代码框架
主函数里对客户端、服务端两种模式进行判断,调用传输、接收两种模式。整体代码由三个部分组成:传输端、接收端和命令行参数解析。客户端每次读取完成之后构造响应,再发送回去。
// 获取命令行选项
bool parseCommandLine(int argc, char* argv[], Options* opt)
// 客户端代码
void transmit(const Options& opt)
// 服务端代码
void receive(const Options& opt)
int main(int argc, char* argv[])
定义了两个重要的数据结构:SessionMessage和PayloadMessage。
// 先发送数据包的个数和长度,告知服务端
struct SessionMessage
{
int32_t number;
int32_t length;
} __attribute__ ((__packed__));//紧凑排列的方式取消字节对齐
// 建立连接后实际发送的数据包
struct PayloadMessage
{
int32_t length;
//防止超长,用最后一个数组表示长度,此长度运行时决定
//分配多少内存,数组就有多长
char data[0];
};
3、带宽计算原理
模仿TCP三次握手的流程,客户端先发送一个数据包,服务端对包进行确认,并且在确认之前客户端不发送下一个包。可以设置发包的数量和每个包的大小,程序计算传输所有包所用的时间,然后计算出带宽。
4、内存优化
因为ttcp以精简为己任,所以在内存上做了一些优化。需要知道柔型数组技术、字节对齐技术等,具体在前面的博文中总结出了:https://xduwq.blog.csdn.net/article/details/118362300
5、讲解视频
TTCP源代码分析
6、源代码
C++版本,关键地方都写了注释。
#include "Acceptor.h"
#include "InetAddress.h"
#include "TcpStream.h"
#include <iostream>
#include <boost/program_options.hpp>
#include <sys/time.h>
struct Options
{
uint16_t port;
int length;
int number;
bool transmit, receive, nodelay;
std::string host;
Options()
: port(0), length(0), number(0),
transmit(false), receive(false), nodelay(false)
{
}
};
// 先发送数据包的个数和长度,告知服务端
struct SessionMessage
{
int32_t number;
int32_t length;
} __attribute__ ((__packed__));//紧凑排列的方式取消字节对齐
// 建立连接后实际发送的数据包
struct PayloadMessage
{
int32_t length;
//防止超长,用最后一个数组表示长度,此长度运行时决定
//分配多少内存,数组就有多长
char data[0];
};
double now()
{
struct timeval tv = { 0, 0 };
gettimeofday(&tv, NULL);//sys/time.h当中,精确获取当前时间
return tv.tv_sec + tv.tv_usec / 1000000.0;
}
// 获取命令行选项
bool parseCommandLine(int argc, char* argv[], Options* opt)
{
// 使用boost,命令行选项功能
namespace po = boost::program_options;
po::options_description desc("Allowed options");
desc.add_options()
("help,h", "Help")
("port,p", po::value<uint16_t>(&opt->port)->default_value(5001), "TCP port")
("length,l", po::value<int>(&opt->length)->default_value(65536), "Buffer length")
("number,n", po::value<int>(&opt->number)->default_value(8192), "Number of buffers")
("trans,t", po::value<std::string>(&opt->host), "Transmit")
("recv,r", "Receive")
("nodelay,D", "set TCP_NODELAY")
;
po::variables_map vm;
try {
po::store(po::parse_command_line(argc, argv, desc), vm);
} catch (...) {
std::cout << "input undecleare options!" <<std::endl;
}
po::notify(vm);
opt->transmit = vm.count("trans");
opt->receive = vm.count("recv");
opt->nodelay = vm.count("nodelay");
if (vm.count("help"))
{
std::cout << desc << std::endl;
return false;
}
if (opt->transmit == opt->receive)
{
printf("either -t or -r must be specified.\\n");
return false;
}
printf("port = %d\\n", opt->port);
if (opt->transmit)
{
printf("buffer length = %d\\n", opt->length);
printf("number of buffers = %d\\n", opt->number);
}
else
{
printf("accepting...\\n");
}
return true;
}
// 客户端代码
void transmit(const Options& opt)
{
InetAddress addr(opt.port);
// 连接地址失败
if (!InetAddress::resolve(opt.host.c_str(), &addr))
{
printf("Unable to resolve %s\\n", opt.host.c_str());
return;
}
printf("connecting to %s\\n", addr.toIpPort().c_str());
TcpStreamPtr stream(TcpStream::connect(addr));//移动语义
if (!stream)
{
printf("Unable to connect %s\\n", addr.toIpPort().c_str());
perror("");
return;
}
if (opt.nodelay)
{
stream->setTcpNoDelay(true);
}
printf("connected\\n");
double start = now();
struct SessionMessage sessionMessage = { 0, 0 };
sessionMessage.number = htonl(opt.number);
sessionMessage.length = htonl(opt.length);
if (stream->sendAll(&sessionMessage, sizeof(sessionMessage)) != sizeof(sessionMessage))
{
perror("write SessionMessage");
return;
}
const int total_len = sizeof(int32_t) + opt.length;
PayloadMessage* payload = static_cast<PayloadMessage*>(::malloc(total_len));
std::unique_ptr<PayloadMessage, void (*)(void*)> freeIt(payload, ::free);
assert(payload);
payload->length = htonl(opt.length);
for (int i = 0; i < opt.length; ++i)
{
payload->data[i] = "0123456789ABCDEF"[i % 16];
}
// 计算时间
double total_mb = 1.0 * opt.length * opt.number / 1024 / 1024;
printf("%.3f MiB in total\\n", total_mb);
for (int i = 0; i < opt.number; ++i)
{
int nw = stream->sendAll(payload, total_len);
assert(nw == total_len);
int ack = 0;
int nr = stream->receiveAll(&ack, sizeof(ack));
assert(nr == sizeof(ack));
ack = ntohl(ack);
assert(ack == opt.length);
}
double elapsed = now() - start;
printf("%.3f seconds\\n%.3f MiB/s\\n", elapsed, total_mb / elapsed);
}
// 服务端代码
void receive(const Options& opt)
{
Acceptor acceptor(InetAddress(opt.port));
TcpStreamPtr stream(acceptor.accept());
if (!stream)
{
return;
}
struct SessionMessage sessionMessage = { 0, 0 };
if (stream->receiveAll(&sessionMessage, sizeof(sessionMessage)) != sizeof(sessionMessage))
{
perror("read SessionMessage");
return;
}
sessionMessage.number = ntohl(sessionMessage.number);
sessionMessage.length = ntohl(sessionMessage.length);
printf("receive buffer length = %d\\nreceive number of buffers = %d\\n",
sessionMessage.length, sessionMessage.number);
double total_mb = 1.0 * sessionMessage.number * sessionMessage.length / 1024 / 1024;
printf("%.3f MiB in total\\n", total_mb);
const int total_len = sizeof(int32_t) + sessionMessage.length;
PayloadMessage* payload = static_cast<PayloadMessage*>(::malloc(total_len));
std::unique_ptr<PayloadMessage, void (*)(void*)> freeIt(payload, ::free);
assert(payload);
double start = now();
for (int i = 0; i < sessionMessage.number; ++i)
{
payload->length = 0;
//验证两个消息长度是否相等
if (stream->receiveAll(&payload->length, sizeof(payload->length)) != sizeof(payload->length))
{
perror("read length");
return;
}
payload->length = ntohl(payload->length);
// 不相等直接崩溃退出
assert(payload->length == sessionMessage.length);
if (stream->receiveAll(payload->data, payload->length) != payload->length)
{
perror("read payload data");
return;
}
// 读取完成之后构造响应,再发送回去
int32_t ack = htonl(payload->length);
if (stream->sendAll(&ack, sizeof(ack)) != sizeof(ack))
{
perror("write ack");
return;
}
}
double elapsed = now() - start;
printf("%.3f seconds\\n%.3f MiB/s\\n", elapsed, total_mb / elapsed);
}
int main(int argc, char* argv[])
{
Options options;
if (parseCommandLine(argc, argv, &options))
{
if (options.transmit)
{
transmit(options);
}
else if (options.receive)
{
receive(options);
}
else
{
assert(0);
}
}
}
参考
以上是关于手把手写C++服务器(11):手撕网络带宽测试工具TTCP的主要内容,如果未能解决你的问题,请参考以下文章
手把手写C++服务器(30):手撕代码——基于TCP/IP的抛弃服务discard
手把手写C++服务器(28):手撕CGI通用网关接口服务器代码
手把手写C++服务器(35):手撕代码——高并发高QPS技术基石之非阻塞send万字长文