当前位置:   article > 正文

学习C++项目—— 搭建多线程网络服务框架,性能测试(并发性能测试,业务性能测试,客户端响应时间测试,网络带宽测试)_c++网络框架

c++网络框架

学习计算机网络编程

一、思路和学习方法

  本文学习于:C语言技术网(www.freecplus.net),在 b 站学习于 C 语言技术网,并加以自己的一些理解和复现,如有侵权会删除。
  接下来对网络编程继续深入学习。通过上篇文章学习,感觉对每个点都记录会很花费时间,但是不记录又对有些地方理解一知半解,综合考虑,先运行出来,对每行代码如何执行要明白,实现什么功能也要明白,freecplus 框架里面知识,后面再仔细学习。

二、网络编程继续深入

2.1 搭建多线程网络服务框架

  使用多线程方式搭建网络服务框架,在实际应用中会广泛一些,但是难度也会高一些。下面开始进行学习,在这之前有一些前置知识,要对多线程网络通信等知识进行学习。其中服务端程序如下,

/*
 * 程序功能:
 * 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include "_freecplus.h"

void *pthmain(void * arg);  // 线程主函数
vector<long> vpthid;  // 存放线程 id 的容器
void mainexit(int sig);  // 信号 2 和 15 的处理函数
void pthmainexit(void * arg);  // 线程清理函数

CLogFile logfile;
CTcpServer TcpServer;  // 创建服务端对象

//  处理业务的主函数
bool _main(const char *strrecvbuffer, char *strsendbuffer);
//  心跳报文
bool biz000(const char *strrecvbuffer, char *strsendbuffer);
//  身份验证业务处理函数
bool biz001(const char *strrecvbuffer, char *strsendbuffer);
//  查询余业务处理函数
bool biz002(const char *strrecvbuffer, char *strsendbuffer);

int main(int argc, char *argv[]){
    if(argc != 3){
      printf("Using:./ExitAndFreeServer port logfile\nExample:./ExitAndFreeServer 5005 /tmp/ExitAndFreeServer.log\n\n"); return -1;
     }

     // 关闭全部的信号,也把僵尸进程关闭
    for(int ii = 0; ii <= 64; ii++) signal(ii, SIG_IGN);

     // 打开日志文件
    if(logfile.Open(argv[2], "a+") == false){
      printf("logfile.Open(%s) failed.\n", argv[2]); return -1;
     }

     // 设置信号,在 shell 状态下可用 “kill + 进程号”正常终止些进程 Ctrl + c
     // 但请不要用 “kill -9 + 进程号”
    signal(SIGINT, mainexit); signal(SIGTERM, mainexit);

     // 初始化 TcpServer 的通信端口
    if(TcpServer.InitServer(atoi(argv[1])) == false){
	 logfile.Write("TcpServer.InitServer(%s) failed. \n", argv[1]); return -1;
     }

    while(true){
       if(TcpServer.Accept() == false){  // 等待客户端连接
	   logfile.Write("TcpServer.Accept() failed. \n"); continue;
         }

         // 以下是子进程,负责与客户端通信
       logfile.Write("客户端(%s)已连接。 \n", TcpServer.GetIP());

       pthread_t pthid;
       if(pthread_create(& pthid, NULL, pthmain, (void *)(long)TcpServer.m_connfd) != 0){
         logfile.Write("pthread_create failed. \n"); return -1; 
         }
       vpthid.push_back(pthid);  //  把线程 id 保存到 vpthid 容器中
    }
   return 0;
}

void *pthmain(void * arg){
  pthread_cleanup_push(pthmainexit, arg);  //  设置线程清理函数
  pthread_detach(pthread_self());  //  分离线程
  pthread_setcanceltype(PTHREAD_CANCEL_DISABLE, NULL);  //  设置取消方式为立即取消

  int socket = (int)(long)arg;  //  客户端的 socket 连接

  int ibuflen = 0;
  char strrecvbuffer[1024], strsendbuffer[1024];  //  存放数据的缓冲区

  while(true){
     memset(strrecvbuffer, 0, sizeof(strrecvbuffer));
     memset(strsendbuffer, 0, sizeof(strsendbuffer));

       // 接收客户端发过来的请求报文
     if(TcpRead(socket, strrecvbuffer, &ibuflen, 50) == false) break;
     logfile.Write("接收:%s \n", strrecvbuffer);

       // 处理业务的主函数
     if(_main(strrecvbuffer, strsendbuffer) == false) break;

     logfile.Write("发送:%s \n", strsendbuffer);
     if(TcpWrite(socket, strsendbuffer) == false) break; // 向客户端回应报文
    }
   pthread_cleanup_pop(1);
   pthread_exit(0);
}

void pthmainexit(void * arg){
   logfile.Write("pthmainexit begin.\n");

    //  关闭与客户端的 socket
   close((int)(long)arg);

    //  从 vpthid 中删除本线程的 id
   for(int ii = 0; ii < vpthid.size(); ii++){
     if(vpthid[ii] == pthread_self()){
        vpthid.erase(vpthid.begin() + ii);
       }
    }
   logfile.Write("pthmainexit end.\n");
}

//  信号 2 和 15 的处理函数
void mainexit(int sig){
  logfile.Write("mainexit begin. \n");
  
   //  关闭监听的 socket
  TcpServer.CloseListen();

   //  取消全部的线程
  for(int ii = 0; ii < vpthid.size(); ii++){
     logfile.Write("cancel %ld\n", vpthid[ii]);
     pthread_cancel(vpthid[ii]);
   }

  logfile.Write("mainexit end.\n");

  exit(0);
}

bool _main(const char * strrecvbuffer, char * strsendbuffer){ // 处理业务的主函数
   int ibizcode = -1;

   GetXMLBuffer(strrecvbuffer, "bizcode", &ibizcode);

   switch(ibizcode){
     case 0: // 心跳
       biz000(strrecvbuffer, strsendbuffer); break;
     case 1: // 身份验证
       biz001(strrecvbuffer, strsendbuffer); break;
     case 2: // 余额查询
       biz002(strrecvbuffer, strsendbuffer); break;

     default:
       logfile.Write("非法报文:%s\n", strrecvbuffer); return false;
    }
   return true;
}

//  身份验证业务处理函数
bool biz001(const char * strrecvbuffer, char * strsendbuffer){
   char username[51], password[51];
   memset(username, 0, sizeof(username));
   memset(password, 0, sizeof(password));

   GetXMLBuffer(strrecvbuffer, "username", username, 50);
   GetXMLBuffer(strrecvbuffer, "password", password, 50);

   if( (strcmp(username, "wucz") == 0) && (strcmp(password, "p@ssw0rd") == 0) )
     sprintf(strsendbuffer, "<retcode>0</retcode><message>成功。</message>");
   else
     sprintf(strsendbuffer, "<retcode>-1</retcode><message>用户名或密码不正确。</message>");

   return true;
}

bool biz002(const char *strrecvbuffer, char *strsendbuffer){
   char cardid[51];
   memset(cardid, 0, sizeof(cardid));

   GetXMLBuffer(strrecvbuffer, "cardid", cardid, 50);

   if(strcmp(cardid, "62620000000001") == 0)
     sprintf(strsendbuffer, "<retcode>0</retcode><message>成功。</message><ye>100.50</ye>");
   else
     sprintf(strsendbuffer, "<retcode>-1</retcode><message>卡号不存在。</message>");

   return true;
}

bool biz000(const char *strrecvbuffer, char *strsendbuffer){
   sprintf(strsendbuffer, "<retcode>0</retcode><message>成功。</message>");

   return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178

  客户端程序不变化,然后运行结果如下,
在这里插入图片描述
  可以看出实现功能满足和多进程实现的一样。里面线程的一些知识,在后面会继续学习,但是其功能能够大致看懂。接下来继续学习。
  其中要注意的点有,使用多进程中和 socketfd 和多线程里面的 socketfd 是不一样的,因此用两种方式来进行通信。这些在 up 主前面视频都讲过。

2.2 性能测试的重要性

  在实际项目开发中,除了完成程序的功能,还需要测试性能。 在充分了解服务端的性能后,才能决定如何选择服务端的框架,还有网络带宽、硬件配置等。 服务端的性能指标是面试中必问的。如果不了解性能指标,面试官会认为你没有实际开发经验或对网络编程一知半解。主要性能指标如下:1. 服务端的并发能力;(可以同时响应多少业务) 2. 服务端的业务处理能力;(每段时间可以响应多少业务请求) 3. 客户端业务响应时效;(响应需要的时间) 4. 网络宽带。(和网络流量请求)

2.3 服务端并发性能测试

  服务端最大并发量,即可以接受客户端连接的最大数量。注意客户端业务请求不要太频繁。重视 CPU 和内存使用率的变化(磁盘 I/O,网络 I/O)。在性能测试时,最好是客户端用一台独立的虚拟机,服务端测试程序用另外一台独立的虚拟机,不然会导致测试不准确。为了学习过程,我就在同一台虚拟机上面跑了,以后在正式测试中再使用其他方法。这里需要注意几个语句,

//  查看进程数量
ps -ef |grep ExitAndFreeServer|wc  

//  运行 sh 脚本文件,和 touch 配合使用 
touch test.sh  //  加入运行脚本文件
sh test.h

//  查看资源内存
free -m

//  查看 cpu 资源
top
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

  其中客户端程序改为如下,只有心跳程序开启,然后运行,

/*
 * 程序功能:
 * 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include "_freecplus.h"

CTcpClient TcpClient;  // 创建服务端对象

bool biz000();  // 发送心跳报文
bool biz001();  // 身份验证
bool biz002();  // 余额查询

int main(int argc, char *argv[]){
  if(argc != 3){
     printf("Using:./client ip port\n Example:./client 127.0.0.1 5005\n\n"); return -1;
   }

  if(TcpClient.ConnectToServer(argv[1], atoi(argv[2])) == false){ //  向服务端发起连接请求
     printf("TcpClient.ConnectToServer(\"%s\", %s) failed.\n", argv[1], argv[2]); return -1;
   }

   /*
   // 身份验证
  if(biz001() == false){
     printf("biz001() failed.\n"); return -1; 
  }
  sleep(10); 
  biz002();  // 余额查询

  sleep(5);
  biz002();  // 余额查询
   */
   
  for(int ii = 0; ii < 10; ii++){
     if(biz000() == false) break;
     sleep(10);
   }
   //  程序直接退出,析构函数会释放资源
}

bool biz001(){  // 
  char strbuffer[1024]; // 存放数据的缓存区

  memset(strbuffer,0,sizeof(strbuffer));
  snprintf(strbuffer, 1000, "<bizcode>1</bizcode><username>wucz</username><password>p@ssw0rd</password>");
  printf("发送:%s\n",strbuffer);
  if (TcpClient.Write(strbuffer) == false) return false; // 向服务端发送请求报文

  memset(strbuffer,0,sizeof(strbuffer));
  if (TcpClient.Read(strbuffer, 20) == false) return false; // 接收服务端的回应报文
  printf("接收:%s\n",strbuffer);

  int iretcode = -1;
  GetXMLBuffer(strbuffer, "retcode", &iretcode);

  if(iretcode == 0){printf("身份验证成功。\n"); return true;}
  printf("身份验证失败。\n");

  return false;
}


bool biz002(){
  char strbuffer[1024];  // 存放数据的缓冲区
  snprintf(strbuffer, 1000, "<bizcode>2</bizcode><cardid>62620000000001</cardid>");
  printf("发送:%s\n", strbuffer);
  if(TcpClient.Write(strbuffer) == false) return false;  // 向服务端发送请求报文

  memset(strbuffer, 0, sizeof(strbuffer));
  if(TcpClient.Read(strbuffer, 20) == false) return false;  // 接收服务端的回应报文

  printf("接收:%s\n", strbuffer);

  int iretcode = -1;
  GetXMLBuffer(strbuffer, "retcode", &iretcode);

  if(iretcode == 0) {printf("查询余额成功。\n"); return true; }

  printf("查询余额成功。\n"); 

  return true;
}

bool biz000(){  // 发送心跳报文
  char strbuffer[1024]; // 存放数据的缓存区

  memset(strbuffer,0,sizeof(strbuffer));
  snprintf(strbuffer, 1000, "<bizcode>0</bizcode>");
  // printf("发送:%s\n",strbuffer);
  if (TcpClient.Write(strbuffer) == false) return false; // 向服务端发送请求报文

  memset(strbuffer,0,sizeof(strbuffer));
  if (TcpClient.Read(strbuffer,20) == false) return false; // 接收服务端的回应报文
  // printf("接收:%s\n",strbuffer);

  return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97

  在这里建立了一个 sh 文件脚本,把客户端程序运行个数 * 4000 ,然后放在 test.sh 文件脚本里,其效果如下。一定要注意的一点,在测试中,最高一次性不要太多,一点一点的加,我为了出效果,直接运行那么多,
在这里插入图片描述
  在同一台虚拟机运行,第一次运行 test.sh 文件时,虚拟机开始有点卡;后来第二次运行 test.sh 文件时,资源不够,然后运行卡壳,系统承载不了那么多进程,其运行结果如下,
在这里插入图片描述

2.4 服务端业务性能测试

  服务端最大业务处理能力,即每秒可以处理的业务请求数量。注重客户端的数量不要太多。重视 CPU 和内存使用率的变化。
  这里对客户端程序改为,

for(int ii = 0; ii < 2000; ii++){
   if(biz000() == false) break;
   usleep(100000);
 }
  • 1
  • 2
  • 3
  • 4

  把 sh 文件中客户端程序运行脚本改为,
在这里插入图片描述
  运行结果如下,
在这里插入图片描述
  可以看出 CPU 消耗和内存消耗,及其进程的情况。然后为了测试每秒接受信号情况,对接收进行抓包,使用 linux 语句为,

grep "2021-10-30 22:11:19 接收" /tmp/ExitAndFreeServer.log|wc
  • 1

  这是在运行过一段时间以后的了,有些客户端程序运行结束了,少了一些,所以运行结果如下,
在这里插入图片描述
  这就是从日志里面抓取的数据接收情况,分析上面运行结果就能够知道,在当下 19 s 接收的情况,相当于 1s 有 60 条记录。可以看出,这样客户端的压力还不够,继续加压,把测试客户端程序改为,

// 用 killall client 杀死所有相关进程
for(int ii = 0; ii < 2000; ii++){
   if(biz000() == false) break;
   usleep(10000);
 }
  • 1
  • 2
  • 3
  • 4
  • 5

  再运行 test.sh 脚本,在运行两次以后,观察到结果如下,
在这里插入图片描述
  能够看到,这样测试时候,CPU 资源已经被占用的还剩 16% 多了,现在压力已经相当大了,也可以查看到相应的进程数和内存情况。在过一会后又恢复到了正常情况。
  为了能够看出处理业务情况,查看服务端数据报文,然后看 1 s 中报文情况,使用语句

grep "2021-10-30 22:41:25 接收" /tmp/ExitAndFreeServer.log|wc
grep "2021-10-30 22:41:21 接收" /tmp/ExitAndFreeServer.log|wc
  • 1
  • 2

  使用语句查看服务端报文情况结果如下
在这里插入图片描述  可以看出,在 1s 中接收到 18107 报文,然后 CPU 还剩资源 10% 多,因此知道服务端压力差不多是 18107 。

2.5 多线程/线程服务端性能差异

  用相同的方法来测试多进程/多线程。测试的方法是一样的,没有什么变化。需要关注内存,CPU 的情况,这里为了学习进度就不进行实践了。得出结论,多线程比多进程在 CPU 和内存消耗情况都是占有优势的。

2.6 测试客户端的响应时间

  客户端业务的响应时间,即是发出业务请求与收到服务端回应的时间间隔,关系到用户的体验。测试环境:1. 业务的闲时时/忙时;2. 不同的网络环境(局域网、互联网、移动通信网络)。需要测试需要一个计时器,需要精确到 微秒。
  这里测试每个业务运行时间,用到了计时器功能,freecplus 把计时器封装好了,然后直接使用就行。用了定时器,来查看每个业务运行的时间,其中客户端程序如下,

/*
 * 程序功能:
 * 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include "_freecplus.h"

CTcpClient TcpClient;  // 创建服务端对象

bool biz000();  // 发送心跳报文
bool biz001();  // 身份验证
bool biz002();  // 余额查询

int main(int argc, char *argv[]){
  if(argc != 3){
     printf("Using:./client ip port\n Example:./client 127.0.0.1 5005\n\n"); return -1;
   }

  CTimer Timer;
  if(TcpClient.ConnectToServer(argv[1], atoi(argv[2])) == false){ //  向服务端发起连接请求
     printf("TcpClient.ConnectToServer(\"%s\", %s) failed.\n", argv[1], argv[2]); return -1;
   }
  printf("TcpClient.ConnectToServer() 耗时%lf\n", Timer.Elapsed());

   // 身份验证
  biz001();
  printf("biz001() 耗时%lf\n", Timer.Elapsed());

  biz002();  // 余额查询
  printf("biz002() 耗时%lf\n", Timer.Elapsed());

  biz000();  // 余额查询
  printf("biz000() 耗时%lf\n", Timer.Elapsed());
   //  程序直接退出,析构函数会释放资源
}

bool biz001(){  // 
  char strbuffer[1024]; // 存放数据的缓存区

  memset(strbuffer, 0, sizeof(strbuffer));
  snprintf(strbuffer, 1000, "<bizcode>1</bizcode><username>wucz</username><password>p@ssw0rd</password>");
  // printf("发送:%s\n",strbuffer);
  if (TcpClient.Write(strbuffer) == false) return false; // 向服务端发送请求报文

  memset(strbuffer,0,sizeof(strbuffer));
  if (TcpClient.Read(strbuffer, 20) == false) return false; // 接收服务端的回应报文
  // printf("接收:%s\n",strbuffer);

  int iretcode = -1;
  GetXMLBuffer(strbuffer, "retcode", &iretcode);

  if(iretcode == 0)return true; //{ printf("身份验证成功。\n"); }
  // printf("身份验证失败。\n");

  return false;
}


bool biz002(){
  char strbuffer[1024];  // 存放数据的缓冲区
  snprintf(strbuffer, 1000, "<bizcode>2</bizcode><cardid>62620000000001</cardid>");
  // printf("发送:%s\n", strbuffer);
  if(TcpClient.Write(strbuffer) == false) return false;  // 向服务端发送请求报文

  memset(strbuffer, 0, sizeof(strbuffer));
  if(TcpClient.Read(strbuffer, 20) == false) return false;  // 接收服务端的回应报文

  // printf("接收:%s\n", strbuffer);

  int iretcode = -1;
  GetXMLBuffer(strbuffer, "retcode", &iretcode);

  if(iretcode == 0)return true;  // {printf("查询余额成功。\n"); return true; }

  // printf("查询余额成功。\n"); 

  return true;
}

bool biz000(){  // 发送心跳报文
  char strbuffer[1024]; // 存放数据的缓存区

  memset(strbuffer,0,sizeof(strbuffer));
  snprintf(strbuffer, 1000, "<bizcode>0</bizcode>");
  // printf("发送:%s\n",strbuffer);
  if (TcpClient.Write(strbuffer) == false) return false; // 向服务端发送请求报文

  memset(strbuffer,0,sizeof(strbuffer));
  if (TcpClient.Read(strbuffer,20) == false) return false; // 接收服务端的回应报文
  // printf("接收:%s\n",strbuffer);

  return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92

  结果如下所示,
在这里插入图片描述

2.7 网络带宽测试

  测试的目的是根据业务的 需求,判断出对网络带宽的要求。测试网络带宽,参考这篇文章:https://www.linuxprobe.com/speedtest-network-in-linux.html。测试网络带宽能承载的业务量,不同的业务对宽带的利用率不一样。要求测试环境的各环节不能存在性能的瓶颈,唯一瓶颈就是网络带宽。注意是只发送数据,不接受回应;上行和下行分开测试。 其中测试服务端程序为,

/*
 * 程序功能:
 * 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include "_freecplus.h"

CTcpServer TcpServer;  // 创建服务端对象

// 程序退出时调用的函数
void FathEXIT(int sig);  // 父进程退出函数
void ChldEXIT(int sig);  // 子进程退出函数

int main(int argc, char *argv[]){
     // 关闭全部的信号,也把僵尸进程关闭
    for(int ii = 0; ii <= 64; ii++) signal(ii, SIG_IGN);

     // 设置信号,在 shell 状态下可用 “kill + 进程号”正常终止些进程 Ctrl + c
     // 但请不要用 “kill -9 + 进程号”
    signal(SIGINT, FathEXIT); signal(SIGTERM, FathEXIT);

     // 初始化 TcpServer 的通信端口
    if(TcpServer.InitServer(5005) == false){
	 printf("TcpServer.InitServer(5005) failed. \n"); FathEXIT(-1);
     }

    while(true){
       if(TcpServer.Accept() == false){  // 等待客户端连接
	     printf("TcpServer.Accept() failed. \n"); continue;
         }
	 // 父进程返回到循环首部
	 if(fork() > 0){TcpServer.CloseClient(); continue; }

	 // 子进程重新设置退出信号
       signal(SIGINT, ChldEXIT); signal(SIGTERM, ChldEXIT);

       TcpServer.CloseListen();
     
         // 以下是子进程,负责与客户端通信
       printf("客户端(%s)已连接。 \n", TcpServer.GetIP());

       char strbuffer[1024];  // 存放数据的缓冲区

       while(true){
         memset(strbuffer, 0, sizeof(strbuffer));
           // 接收客户端发过来的请求报文
         if(TcpServer.Read(strbuffer, 50) == false) break;
         printf("接收:%s \n", strbuffer);

     	   strcat(strbuffer, "ok"); // 在客户端的报文后加上“ok”
	   printf("发送:%s \n", strbuffer);
	   if(TcpServer.Write(strbuffer) == false)break; // 向客户端回应报文
         }
       printf("客户端已断开。 \n");  // 程序直接退出,析构函数会释放资源
       ChldEXIT(-1); // 通信完成后,子进程退出。
     }
}

void FathEXIT(int sig){  // 父进程退出函数
    if(sig > 0){
         // 免除不再受到其他信号的打扰
       signal(sig, SIG_IGN);signal(SIGINT, SIG_IGN);signal(SIGTERM, SIG_IGN);
       printf("catching the signal(%d). \n", sig);
     }

    kill(0, 15);  // 通知其它的子进程退出。
    printf("父进程退出。 \n");

    // 编写善后代码(释放资源、提交或回滚事务)
   TcpServer.CloseClient();

   exit(0);
}

void ChldEXIT(int sig){ // 子进程退出函数
    if(sig > 0){
       signal(sig, SIG_IGN);signal(SIGINT, SIG_IGN);signal(SIGTERM, SIG_IGN);
     }

    printf("子进程退出。 \n");

    // 编写善后代码(释放资源、提交或回滚事务)
   TcpServer.CloseClient();

   exit(0);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

  测试客户端程序为,

/*
 * 程序功能:
 * 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include "_freecplus.h"

CTcpClient TcpClient;  // 创建服务端对象

bool biz000();  // 发送心跳报文
bool biz001();  // 身份验证
bool biz002();  // 余额查询

int main(int argc, char *argv[]){
  if(argc != 3){
     printf("Using:./client ip port\n Example:./client 127.0.0.1 5005\n\n"); return -1;
   }

  CTimer Timer;
  if(TcpClient.ConnectToServer(argv[1], atoi(argv[2])) == false){ //  向服务端发起连接请求
     printf("TcpClient.ConnectToServer(\"%s\", %s) failed.\n", argv[1], argv[2]); return -1;
   }
  printf("TcpClient.ConnectToServer() 耗时%lf\n", Timer.Elapsed());

  for(int ii = 0; ii < 10000; ii++){
     biz001(); 
   }

   // 身份验证
  biz001();
  printf("biz001() 耗时%lf\n", Timer.Elapsed());

  return 0;
   
  biz002();  // 余额查询
  printf("biz002() 耗时%lf\n", Timer.Elapsed());

  biz000();  // 余额查询
  printf("biz000() 耗时%lf\n", Timer.Elapsed());
   //  程序直接退出,析构函数会释放资源
}

bool biz001(){  // 
  char strbuffer[1024]; // 存放数据的缓存区

  memset(strbuffer, 0, sizeof(strbuffer));
  snprintf(strbuffer, 1000, "<bizcode>1</bizcode><username>wucz</username><password>p@ssw0rd</password>");
  // printf("发送:%s\n",strbuffer);
  if (TcpClient.Write(strbuffer) == false) return false; // 向服务端发送请求报文

  memset(strbuffer,0,sizeof(strbuffer));
  if (TcpClient.Read(strbuffer, 20) == false) return false; // 接收服务端的回应报文
  // printf("接收:%s\n",strbuffer);

  int iretcode = -1;
  GetXMLBuffer(strbuffer, "retcode", &iretcode);

  if(iretcode == 0)return true; //{ printf("身份验证成功。\n"); }
  // printf("身份验证失败。\n");

  return false;
}


bool biz002(){
  char strbuffer[1024];  // 存放数据的缓冲区
  snprintf(strbuffer, 1000, "<bizcode>2</bizcode><cardid>62620000000001</cardid>");
  // printf("发送:%s\n", strbuffer);
  if(TcpClient.Write(strbuffer) == false) return false;  // 向服务端发送请求报文

  memset(strbuffer, 0, sizeof(strbuffer));
  if(TcpClient.Read(strbuffer, 20) == false) return false;  // 接收服务端的回应报文

  // printf("接收:%s\n", strbuffer);

  int iretcode = -1;
  GetXMLBuffer(strbuffer, "retcode", &iretcode);

  if(iretcode == 0)return true;  // {printf("查询余额成功。\n"); return true; }

  // printf("查询余额成功。\n"); 

  return true;
}

bool biz000(){  // 发送心跳报文
  char strbuffer[1024]; // 存放数据的缓存区

  memset(strbuffer,0,sizeof(strbuffer));
  snprintf(strbuffer, 1000, "<bizcode>0</bizcode>");
  // printf("发送:%s\n",strbuffer);
  if (TcpClient.Write(strbuffer) == false) return false; // 向服务端发送请求报文

  memset(strbuffer,0,sizeof(strbuffer));
  if (TcpClient.Read(strbuffer,20) == false) return false; // 接收服务端的回应报文
  // printf("接收:%s\n",strbuffer);

  return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98

  运行结果如下,
在这里插入图片描述
  可以看出在双向发送和接收数据时,发送 10000 个心跳业务双向时间情况。现在改为单向测试,客户端只发送不接收了;服务端只接收不发送了。

// 服务端
while(true){
  memset(strbuffer, 0, sizeof(strbuffer));
    // 接收客户端发过来的请求报文
  if(TcpServer.Read(strbuffer, 50) == false) break;
  printf("接收:%s \n", strbuffer);

     /*
  strcat(strbuffer, "ok"); // 在客户端的报文后加上“ok”
printf("发送:%s \n", strbuffer);
if(TcpServer.Write(strbuffer) == false)break; // 向客户端回应报文
     */
}

// 客户端
bool biz001(){  // 
  char strbuffer[1024]; // 存放数据的缓存区

  memset(strbuffer, 0, sizeof(strbuffer));
  snprintf(strbuffer, 1000, "<bizcode>1</bizcode><username>wucz</username><password>p@ssw0rd</password>");
  // printf("发送:%s\n",strbuffer);
  if (TcpClient.Write(strbuffer) == false) return false; // 向服务端发送请求报文
  
  return true;
  
  memset(strbuffer,0,sizeof(strbuffer));
  if (TcpClient.Read(strbuffer, 20) == false) return false; // 接收服务端的回应报文
  // printf("接收:%s\n",strbuffer);

  int iretcode = -1;
  GetXMLBuffer(strbuffer, "retcode", &iretcode);

  if(iretcode == 0)return true; //{ printf("身份验证成功。\n"); }
  // printf("身份验证失败。\n");

  return false;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

  现在查看运行效果,
在这里插入图片描述
  可以看出单向测试效果还是不错的,直接体现了 TCP 单向传输带宽业务情况。我感觉 UP 主要讲了网络测试的方法,还是比较直接但是不太深入,以后至少遇到时候知道怎么处理,学习还在比较表面,以后在项目中再继续理解。

三、总结

  后续继续续学习网络编程知识,现在进行了多线程服务框架测试,能够更好了解网络编程的性能基本情况。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/1014152
推荐阅读
相关标签
  

闽ICP备14008679号