socket网络通信实现与优化

多进程通信

进程通信方式

  1. 管道:
  2. 消息队列
  3. 信号
  4. 共享内存
  5. 信号量
  6. 套接字socket

多进程通信的优化

  1. 将socket通信的客户端以及服务端进行封装,让代码更清晰高效
  2. 解决了僵尸进程的问题,防止子进程占用过多的内存资源
  3. 关闭多余的socket,防止系统打开的文件描述符过多:ulimit -a
  4. 程序退出资源的释放,利用exit(0)退出程序,调用析构函数,释放资源
  5. 多进程服务进程的运行日志,多进程日志文件不能切换。一般分开写日志
  6. gdb对多进程进行调试。
  7. 增加业务代码:实现登录用户与密码验证

服务端程序

#include "../_freecplus.h"

CTcpServer TcpServer;   // 创建服务端对象。
CLogFile logfile;
// 程序退出时调用的函数
 void FathEXIT(int sig);
 void ChldEXIT(int sig);
// 父进程退出时调用的函数
 void FathEXIT(int sig)
 {
   if(sig>0)
     {
       signal(sig,SIG_IGN);
       signal(SIGINT,SIG_IGN);
       signal(SIGTERM,SIG_IGN);
       printf("catch the signal %d\n",sig);
     }
   kill(0,15);//父进程退出,子进程也全部退出
   printf("父进程退出\n");
   exit(0);
 }
 void ChldEXIT(int sig)
{
   if(sig>0)
   {
       signal(sig,SIG_IGN);
       signal(SIGINT,SIG_IGN);
       signal(SIGTERM,SIG_IGN);
   }
   printf("子进程退出\n");
   exit(0);
}

int main(int argc,char *argv[])
{
  //关闭全部的信号
  logfile.Open("/tmp/log/demo1.log","a+");
  for(int i=0;i<100;i++)
  {
    signal(i,SIG_IGN);
  }

  //设置信号,在shell下可用kill+进程号正常终止进程

  signal(SIGINT,FathEXIT);signal(SIGTERM,FathEXIT);
  signal(SIGCHLD,SIG_IGN);//忽略子进程退出的信号,避免产生僵尸进程

  if (TcpServer.InitServer(5005)==false) // 初始化TcpServer的通信端口。
  {
  //  printf("TcpServer.InitServer(5005) failed.\n"); return -1;
    logfile.Write("TcpServer.InitServer(5005) failed.\n");return -1;
  }
while(true)
{
  if (TcpServer.Accept()==false)   // 等待客户端连接。
  {
    printf("TcpServer.Accept() failed.\n"); return -1;
  }

  //printf("客户端(%s)已连接。\n",TcpServer.GetIP());
  logfile.Write("客户端(%s)已经连接。\n",TcpServer.GetIP());
if(fork()>0)
{
   TcpServer.CloseClient();//父进程关闭多余的客户端连接上来的socket
   continue;
}
  TcpServer.CloseListen();//子进程关闭监听的socket 
  char strbuffer[1024];  // 存放数据的缓冲区。
  signal(SIGINT,ChldEXIT);signal(SIGTERM,ChldEXIT);

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

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

  //printf("客户端已断开。\n");    // 程序直接退出,析构函数会释放资源。
  logfile.Write("客户端已经断开。\n");
  exit(0);
}
}

客户端程序

#include "../_freecplus.h"

int main(int argc,char *argv[])
{
  CTcpClient TcpClient;   // 创建客户端的对象。

  if (TcpClient.ConnectToServer("192.168.74.10",5005)==false) // 向服务端发起连接请求。
  {
    printf("TcpClient.ConnectToServer(\"172.16.0.15\",5858) failed.\n"); return -1;
  }

  char strbuffer[1024];    // 存放数据的缓冲区。
  printf("pid=%d\n",getpid());
  for (int ii=0;ii<20;ii++)   // 利用循环,与服务端进行5次交互。
  {
    memset(strbuffer,0,sizeof(strbuffer));
    snprintf(strbuffer,50,"pid=%d:这是第%d个超级女生,编号%03d。",getpid(),ii+1,ii+1);
    printf("发送:%s\n",strbuffer);
    if (TcpClient.Write(strbuffer)==false) break;    // 向服务端发送请求报文。

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

    sleep(1);
  }

  // 程序直接退出,析构函数会释放资源。
}

运行窗口

  1. 服务端后台运行
    在这里插入图片描述
  2. 客户端发起连接
    在这里插入图片描述
  3. 查看服务端运行的进程,无僵尸进程的产生
    在这里插入图片描述
  4. 查看服务端的打印日志,日志正常打印输出
    在这里插入图片描述
  5. 杀死父进程
    在这里插入图片描述
    在这里插入图片描述
  6. 业务代码
    服务端业务主体代码
    while (true)
    {
      memset(strrecvbuffer,0,sizeof(strrecvbuffer));
      memset(strsendbuffer,0,sizeof(strsendbuffer));

      if (TcpServer.Read(strrecvbuffer,30)==false) break; // 接收客户端发过来的请求报文。
      logfile.Write("接收:%s\n",strrecvbuffer);

      // 处理业务的主函数。
      if (_main(strrecvbuffer,strsendbuffer)==false) ChldEXIT(-1);

      logfile.Write("发送:%s\n",strsendbuffer);
      if (TcpServer.Write(strsendbuffer)==false) break;     // 向客户端回应报文。
    }

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,"wwc")==0) && (strcmp(password,"19960207")==0) )
    sprintf(strsendbuffer,"<retcode>0</retcode><message>成功。</message>");
  else
    sprintf(strsendbuffer,"<retcode>-1</retcode><message>用户名或密码不正确。</message>");

  return true;
}

客户端主体代码

while(1){
     if(biz001()==false);
     break;
   }
// 身份验证。
bool biz001()
{
  char strbuffer[1024];    // 存放数据的缓冲区。

  memset(strbuffer,0,sizeof(strbuffer));
char name[10];
memset(name,0,sizeof(name));
char password[10];
memset(password,0,sizeof(password));
printf("请输入用户名:");
scanf("%s",name);
printf("请输入密码:");
scanf("%s",password);
  snprintf(strbuffer,1000,"<bizcode>1</bizcode><username>%s</username><password>%s</password>",name,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;
}

在这里插入图片描述

多线程通信

多线程服务程序退出

思路

  1. 在主进程中设置程序退出的信号2 和15的信号处理函数
  2. 在信号处理函数中取消主进程的监听,设置全部的线程取消
  3. 在线程处理函数中首先注册线程清理函数,将线程设置为分离,将取消类型设置为(立即取消、延迟取消),并且最后执行线程清理函数(注册清理函数和执行函数成对出现)
  4. 编写清理函数,将线程连接的socket关闭
    在这里插入图片描述
    需要注意的点:收到2,15信号,执行信号函数取消同样会调用线程清理函数,我这里usleep(200),即可发现这个问题

服务端代码

#include "../_freecplus.h"
void* pthmain(void* arg);
void mainexit(int sig);
vector<pthread_t>vpthid;
CTcpServer TcpServer;   // 创建服务端对象。
int main(int argc,char *argv[])
{ 
  signal(2,mainexit);signal(5,mainexit); 
  if (TcpServer.InitServer(5005)==false) // 初始化TcpServer的通信端口。
  { 
    printf("TcpServer.InitServer(5858) failed.\n"); return -1;
  }
 while(true)
 { 
  if (TcpServer.Accept()==false)   // 等待客户端连接。
  { 
    printf("TcpServer.Accept() failed.\n"); return -1;
  }
  printf("客户端(%s)已连接。\n",TcpServer.GetIP());
  pthread_t pthid1;
  if(pthread_create(&pthid1,NULL,pthmain,(void*)(long)TcpServer.m_connfd)!=0)
  {
     printf("creat pthid1 fail\n");
     return -1;
  }
  vpthid.push_back(pthid1);
 }
   return 0;
}
//2,15信号处理函数
void mainexit(int sig)
{
  printf("mainexit begin.\n");
  TcpServer.CloseListen();//关闭监听的socket
  for(int i=0;i<vpthid.size();i++)
  {
      printf("cancel %ld\n",vpthid[i]);
      pthread_cancel(vpthid[i]);//取消全部的线程
  }
  printf("mainexit end.\n");
  usleep(20);
  exit(0);
}
//设置线程清理函数
void pthmainexit(void* arg)
{
   printf("线程清理函数开始。\n");
  close((int)(long)arg);//关闭线程连接的socket 
  for(int i=0;i<vpthid.size();i++)
  {
    if(vpthid[i]==pthread_self())
     {
       vpthid.erase(vpthid.begin()+i);
     }
  }
  printf("线程清理函数结束.\n");
}
void* pthmain(void* arg)
{
  pthread_cleanup_push(pthmainexit,arg);//注册线程清理函数
  pthread_detach(pthread_self());
  pthread_setcanceltype(PTHREAD_CANCEL_DISABLE,NULL);
  int sockfd=(int)(long)(arg);
  while (true)
  {
    char strbuffer[1024];  // 存放数据的缓冲区。
    int buflen=0;
    memset(strbuffer,0,sizeof(strbuffer));
    if (TcpRead(sockfd,strbuffer,&buflen,300)==false) break; // 接收客户端发过来的请求报文。
    printf("接收:%s\n",strbuffer);

    strcat(strbuffer,"ok");      // 在客户端的报文后加上"ok"。
    printf("发送:%s\n",strbuffer);
    if (TcpWrite(sockfd,strbuffer,buflen)==false) break;     // 向客户端回应报文。
  }
  printf("客户端已断开。\n");
  pthread_cleanup_pop(1);//执行线程清理函数
}

运行窗口
在这里插入图片描述

性能测试

服务端并发量测试

  • 实验环境:两台独立的虚拟机,客户端给服务端只发送心跳报文,测试1000台客户端连接服务端后,服务端均为多进程,查看服务端内存和cpu的变化情况
    实验前
    cpu的运行情况:
    在这里插入图片描述
    内存的消耗情况:
    在这里插入图片描述
    实验后
    服务端运行的进程数
    在这里插入图片描述
    cpu的运行情况:
    在这里插入图片描述
    内存的消耗情况:
    在这里插入图片描述

服务端业务性能测试

  • 实验环境:两台独立的虚拟机,客户端给服务端只发送心跳报文,测试100台客户端同时连接服务端后,服务端均为多进程,查看服务端cpu的变化情况,以及每秒接受的信息量。
    == 客户端业务==
  for (int ii=0;ii<2000;ii++)
  {
    if (biz000()==false) break;

    usleep(10000);
  }
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;
}

服务端消耗内存
之前:
之前:
之后:
在这里插入图片描述
结论:消耗内存大概为16M
服务端cpu情况
在这里插入图片描述
服务端每秒接收的数据量:
在这里插入图片描述

多进程/多线程服务端性能

多线程:
内存消耗对比:
之前:
在这里插入图片描述
之后:
在这里插入图片描述
cpu消耗情况:
在这里插入图片描述
多线程1s处理报文的数量:
在这里插入图片描述

结论:1.连接相同客户端,处理相同的业务,多线程消耗内存为7M,多进程为16M
2.多进程cpu剩余30%,多进程剩余20%
3.多进程和多线程1s处理的报文数基本相同,都为8000多。

客户端业务响应时间

环境:两台centos7局域网上客户端在不同阶段的测试响应时间
1.客户端
2.客户端给服务端发送心跳报文,此时服务端闲时,计算从发送到接受的耗时
在这里插入图片描述
3.客户端给服务端发送心跳报文,此时服务端忙时,计算从发送到接受的耗时
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alex1_Code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值