IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 手把手写C++服务器(11):手撕网络带宽测试工具TTCP -> 正文阅读

[C++知识库]手把手写C++服务器(11):手撕网络带宽测试工具TTCP

前言:TTCP诞生于1984年,是Unix很流行的网络带宽测试工具,并从中衍生出了著名的网络测试工具Ipref。TTCP手撕的源代码量比较小,是网络编程入门的最佳练习demo。本篇文章参考陈硕的TTCP,进行学习分析。

目录

1、program_options控制命令行输入

2、代码框架

3、带宽计算原理

4、内存优化

5、讲解视频

6、源代码

参考


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++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-04 21:12:22  更:2021-07-04 21:12:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/4 8:27:24-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码