美文网首页
【C/C++】项目_10_APP软件服务端(client.cpp

【C/C++】项目_10_APP软件服务端(client.cpp

作者: linuxCoder | 来源:发表于2020-07-15 17:26 被阅读0次

@TOC


1.APP需求和架构:多进程

如下手机开发人员可以获得用户的手机现在采用的网络是wifi还是2G.....获得这些数据后对我们服务端后台很重要,若采用wifi后台处理方式不一样。天气实况(温压湿...)就是从全国分钟数据里取的,最多存3天就可以满足手机app需求,图片用linux下工具imagemagick用system函数调用。最下面一层是iphone等是客户端

在这里插入图片描述
测试多任务下文件操作和测试多任务下socket操作:多进程fp指针变量会复制多份,往一个文件里写会出问题,比如多进程日志切换(logfile.Open里第三个参数备份为true:关闭一文件再往新文件里写,其他进程在关闭的文件里写时会出问题),数据库连接池conn底层也是socket
在这里插入图片描述
如下一个进程关了,其他进程没有关,大小不够没有日志切换,继续写不出问题
在这里插入图片描述
make demo21,./demo21,如下没出问题
在这里插入图片描述
如下log文件为空,多进程不能关闭后再打开,所以多进程logfile.Open里第三个参数备份应为false不能切换
在这里插入图片描述
如下做日志切换会丢掉很多东西
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
数据库操作也是写文件,事务约束等检查效率比文件系统慢
在这里插入图片描述
在这里插入图片描述
如下demo21多进程
在这里插入图片描述
//demo22.cpp
#include "_public.h"
CLogFile logfile;
void *pth1_main(void *arg)
{
  for (int jj=0;jj<200000;jj++)
  {
    logfile.Write("pid=%10d,jj=%10d\n",getpid(),jj);
  }
  pthread_exit(0);
}

int main(int argc,char *argv[])
{
  logfile.Open("/tmp/demo22.log","w",true,true);
  for (int ii=0;ii<50;ii++)
  {
    pthread_t pthid;
    pthread_create(&pthid,NULL,pth1_main,NULL);
  }  
  sleep(60); //50个线程写完运行完大概需1分钟,主进程sleep1分钟再退让线程都能写完
  return 0;
}

如下未切换日志,50个线程写未出问题

在这里插入图片描述
如下切换日志logfile.Open第三个参数为true时,如上demo22.log文件490M,_public.cpp中改为100M。logfile.Open第四个参数一般为false,root用户userdel robert删不了因为有程序运行,su -robert禁用crontab再exit测试,多线程日志切换不受影响,多进程日志切换受影响
在这里插入图片描述
在这里插入图片描述
测试多任务下socket操作:如下注释掉fork()变为单进程进程号都为15878,demo25服务端recv到的再加上resp返回去
在这里插入图片描述
// 客户端demo23.cpp
#include "_public.h"
CTcpClient TcpClient;
CLogFile logfile;

int main(int argc,char *argv[])
{
  logfile.Open("/tmp/demo23.log","w",false);
  if (TcpClient.ConnectToServer("127.0.0.1",5015)==false) { printf("conn failed.\n"); return -1; }
  char strSendBuffer[301],strRecvBuffer[301];
  fork(); //fork出一个进程+父进程=两个进程

  for (int ii=0;ii<20;ii++)  //发20个报文
  {
    memset(strSendBuffer,0,sizeof(strSendBuffer));
    memset(strRecvBuffer,0,sizeof(strRecvBuffer));
    sprintf(strSendBuffer,"this is %10d(%10d)",ii,getpid());

    logfile.Write("send=%s=\n",strSendBuffer);
    if (TcpClient.Write(strSendBuffer)==false) { printf("send failed.\n"); return -1; }
    sleep(1);

    if (TcpClient.Read(strRecvBuffer,80)==false)  { printf("recv failed.\n"); return -1; }
    logfile.Write("recv=%s,%10d=\n",strRecvBuffer,getpid());
  } 
  return 0;
}
//demo25.cpp服务端
#include "_public.h"
CTcpServer TcpServer;
CLogFile logfile;

int main(int argc,char *argv[])
{
  logfile.Open("/tmp/demo25.log","w",false);
  if (TcpServer.InitServer(5015)==false) { printf("init failed.\n"); return -1; }
  TcpServer.Accept();
  char strSendBuffer[301],strRecvBuffer[301];

  for (int ii=0;ii<10;ii++)
  {
    memset(strSendBuffer,0,sizeof(strSendBuffer));
    memset(strRecvBuffer,0,sizeof(strRecvBuffer));

    if (TcpServer.Read(strRecvBuffer,80)==false)  { printf("recv failed.\n"); return -1; }
    logfile.Write("recv=%s=\n",strRecvBuffer);

    sprintf(strSendBuffer,"%s resp",strRecvBuffer); 
    logfile.Write("send=%s=\n",strSendBuffer);
    if (TcpServer.Write(strSendBuffer)==false) { printf("send failed.\n"); return -1; }
  }
  return 0;
}

make ,./demo25,./demo23,vi /tmp/demo23.log正常显示,因为报文数据量不够,冲突没有体现出来,如果数据量够且收发速度快,则会冲突,如下在客户端中

在这里插入图片描述
在这里插入图片描述
两个客户端进程接收时冲突
在这里插入图片描述
如下在客户端中两个进程都可发送,接收只让一个进程干。服务端永远一个进程
在这里插入图片描述
多进程socket连接不会用来共享,返回不知道给哪个客户端进程,多进程用信号灯加锁麻烦,一般用多线程并加锁。数据库连接也是socket服务端,不可能让多个进程同时写如事务。多个线程可共用socket连接(协调单个发),但不能说多个线程共享一个socket连接(一起发),一个socket连接同一时间只能有一个线程使用,用完后提交事务,连接才释放出来
在这里插入图片描述
如下开始APP服务端设计,客户端就是手机app软件。第一次客户端将手机编号传给服务端,服务端将站点信息传给客户端。客户端得到日出日落时间分180份,现在时间-日出时间大概知道在原点什么位置,都由客户端做,传图就是传文件
在这里插入图片描述
短连接:客户端即用户点击按钮一次建立一次socket连接请求,处理完一个就断开,响应慢:建立一次socket连接费时间,服务端fork一个进程也要时间,之后和数据库连接也要时间
长连接:客户端与服务端socket一直连接着进行数据通信,没有数据通信时用心跳(之前文件传输都用的是长连接),用户关了app,连接才断开。费服务端资源:长连接连上后,数据库连接和进程都已准备好,一直通信完才断开。但响应快,用户看到数据越快越好控制在1秒内。如下项目组织(shtqapp是一个独立的项目)
在这里插入图片描述
如下是数据结构设计
在这里插入图片描述
如下不需收集用户密码,T_USERINFO表和T_OBTCODE表放入shtqapp数据库用户里,T_USERLOG表放入shappdata数据库用户里
在这里插入图片描述
如下用户使用日志表用户位置经纬海拔高度
在这里插入图片描述
如下preview的sql语句是建表
在这里插入图片描述
以下是多进程,正式上线时用http80端口(用户在某个网络内部80端口肯定开放,不开放外网上不了,qq微信虽不是用http协议但也采用80端口)
// client.cpp,模拟tcp手机客户端,客户端用短链接还是长连接由客户端自己安排
#include "_freecplus.h"
CTcpClient TcpClient;
char strSendBuffer[301],strRecvBuffer[301];
bool biz10000();  // 心跳
bool biz10001();   // 新用户登录:只传个设备编号id,服务端把城市站点信息传给客户端,手机利用定位匹配

int main(int argc,char *argv[])
{
  //if (TcpClient.ConnectToServer("127.0.0.1",5015)==false) { printf("conn failed.\n"); return -1; }
  if (TcpClient.ConnectToServer("172.16.0.15",5015)==false) { printf("conn failed.\n"); return -1; }
  //if (TcpClient.ConnectToServer("118.89.50.198",5015)==false) { printf("conn failed.\n"); return -1; }
  if (biz10000()==false) return 0;   // 心跳
  CTimer Timer;
  if (biz10001()==false) return 0;   // 新用户登录 
  printf("biz10001=%lf\n",Timer.Elapsed());
  sleep(1);  
  return 0;
}

bool biz10000()
{
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  memset(strRecvBuffer,0,sizeof(strRecvBuffer));
  strcpy(strSendBuffer,"<bizid>10000</bizid>");
  //printf("send=%s=\n",strSendBuffer);
  if (TcpClient.Write(strSendBuffer)==false) { printf("send failed.\n"); return false; }
  if (TcpClient.Read(strRecvBuffer,20)==false)  { printf("recv failed.\n"); return false; }
  //printf("recv=%s=\n",strRecvBuffer);
  return true;
}

bool biz10001()
{
  memset(strSendBuffer,0,sizeof(strSendBuffer)); 
  memset(strRecvBuffer,0,sizeof(strRecvBuffer));
  // 如下请求报文
  strcpy(strSendBuffer,"<bizid>10001</bizid><userid>52:54:00:83:0f:c1</userid><ttytype>1</ttytype><lat>20.234518</lat><lon>115.90832</lon><height>150.5</height>");
  //printf("send=%s=\n",strSendBuffer);
  if (TcpClient.Write(strSendBuffer)==false) { printf("send failed.\n"); return false; }
  //如下用一个循环接收全部的站点信息
  while (1)
  {
    memset(strRecvBuffer,0,sizeof(strRecvBuffer));
    if (TcpClient.Read(strRecvBuffer,20)==false)  { printf("recv failed.\n"); return false; }
    // printf("recv=%s=\n",strRecvBuffer); //手机端没数据库,手机软件真正处理方法把数据保存到xml文件里
    if (strcmp(strRecvBuffer,"ok")==0) break; //接收到ok的话表示数据处理完了
  }
  return true;
}
// 上海天气APP软件服务端主程序shtqappserver.cpp多进程。
#include "_public.h"
#include "_ooci.h"
// 业务请求
struct st_biz
{
  int  bizid;               // 业务代码
  char userid[51];          // 设备ID
  int  ttytype;             // 用户的设备类型,0-未知;1-IOS;2-Andriod,2-鸿蒙。
  int  usertype;            // 用户分类,0-未知;1-普通用户;2-气象志愿者;3-内部用户。
  double lon;
  double lat;
  double height;
  char   obtid[11];
  char   xmlbuffer[1001];
} stbiz;
// 把xml解析到参数stbiz结构中
void xmltobiz(char *strxmlbuffer);
CTcpServer TcpServer;
CLogFile   logfile;
connection conn;
//如上定义为全局变量共享但不会在main函数主进程里连数据库,在子进程里连。如果在主进程连,子进程用是不行的
//conn若定义为局部变量,ChldEXIT()中conn不会调用析构函数,全局会调用释放资源。
//CTcpServer析构函数会自动调用CloseClient()方法关闭socket连接
char strRecvBuffer[TCPBUFLEN+10]; // 接收报文的缓冲区
char strSendBuffer[TCPBUFLEN+10]; // 发送报文的缓冲区
// 程序退出时调用的函数
void FathEXIT(int sig);
void ChldEXIT(int sig);
// 业务处理进程主函数
void ChldMain();
// 心跳业务
bool biz10000();
// 新用户登录业务,用户基本信息表和用户使用日志都插入一条记录,select全国气象站点数据返回给客户端
bool biz10001();
// 获取天气实况
bool biz10002();
// 插入用户请求日志表
bool InsertUSERLOG();

int main(int argc,char *argv[])
{
  if (argc != 3)
  {
    printf("\n");
    printf("Using:/htidc/shtqapp/bin/shtqappserver logfilename port\n");

    printf("Example:/htidc/shtqapp/bin/shtqappserver /log/shtqapp/shtqappserver.log 5015\n\n");
    printf("本程序是上海天气APP软件的服务端。\n");
    printf("logfilename 日志文件名。\n");
    printf("port 用于传输文件的TCP端口。\n");
    return -1;
  }
  // 关闭全部的信号和输入输出
  // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程
  // 但请不要用 "kill -9 +进程号" 强行终止
  CloseIOAndSignal(); signal(SIGINT,FathEXIT); signal(SIGTERM,FathEXIT);
  // 打开程序运行日志,这是一个多进程程序,日志不能自动切换
  if (logfile.Open(argv[1],"a+",false) == false)
  {
    printf("logfile.Open(%s) failed.\n",argv[1]); return -1;
  }
  logfile.Write("shtqappserver started(%s).\n",argv[2]);
  if (TcpServer.InitServer(atoi(argv[2])) == false)
  {
    logfile.Write("TcpServer.InitServer(%s) failed.\n",argv[2]); exit(-1);
  }
  while (true)
  {
    // 等待客户端的连接
    if (TcpServer.Accept() == false)
    {
      logfile.Write("TcpServer.Accept() failed.\n"); continue;
    }
    // 新的客户端连接来fork一个进程
    if (fork() > 0)
    {
      // 父进程关闭刚建立起来的sock连接,并回到Accept继续监听
      TcpServer.CloseClient(); continue;
    }
    // 进入子进程的流程
    signal(SIGINT,ChldEXIT); signal(SIGTERM,ChldEXIT);
    // 子进程需要关掉监听的sock
    TcpServer.CloseListen();
    // 子进程业务处理进程主函数
    ChldMain();
    ChldEXIT(0);
  }
  return 0;
}

// 父进程退出时调用的函数
void FathEXIT(int sig)
{
  if (sig > 0) //如果收到信号就屏蔽信号
  {
    signal(sig,SIG_IGN); signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
    logfile.Write("catching the signal(%d).\n",sig);
  }
  kill(0,15); //通知全部子进程退出
  logfile.Write("shtqappserver EXIT.\n");
  exit(0);
}

// 子进程退出时调用的函数
void ChldEXIT(int sig)
{
  if (sig > 0)  //如果不屏蔽的话会收到两个退出信号
  {
    signal(sig,SIG_IGN); signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
  }
  exit(0);
}

// 业务处理进程主函数
void ChldMain()
{
  while (true)
  {
    memset(strRecvBuffer,0,sizeof(strRecvBuffer));
    memset(strSendBuffer,0,sizeof(strSendBuffer));
    // 接收客户端的业务请求报文,如果返回false,认为是客户端退出或网络原因,直接return不写错误日志
    if (TcpServer.Read(strRecvBuffer,50) == false)
    {
      // 比如用户手机app回到桌面,程序挂起不需要app体面退出,Read失败很多不用写日志
      // logfile.Write("TcpServer.Read() failed.\n"); 
      return;
    }
    logfile.Write("strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx     
    // 把参数解析出来,客户端与服务端通信全用xml,每种业务请求弄一个业务编号
    xmltobiz(strRecvBuffer);
    if (stbiz.bizid==10000)    // 心跳报文
    {
      if (biz10000()==true) continue;
      else return;
    }
    CTimer Timer;
    // 连接数据库,第三个参数为true自动提交
    if (conn.connecttodb("shtqapp/pwdidc@snorcl11g_198","Simplified Chinese_China.ZHS16GBK",true)!=0)
    {
      logfile.Write("conn.connettodb() failed.\n"); return;
    }
    logfile.Write("conn=%lf\n",Timer.Elapsed());
    // 新用户登录 
    if (stbiz.bizid==10001)    
    {
      if (biz10001()==true) continue;
      else return;
    }
    // 获取天气实况
    if (stbiz.bizid==10002)    
    {
      if (biz10002()==true) continue;
      else return;
    }
    // 协议:你发什么给我,我发什么给你。报文格式不符合要求的话直接return
    logfile.Write("非法报文%s\n",strRecvBuffer); return;
  }
}

// 把xml解析到参数starg结构中,客户端全部请求放入starg数据结构里
void xmltobiz(char *strxmlbuffer)
{
  memset(&stbiz,0,sizeof(struct st_biz));
  // 客户端发给服务端全部报文都会有个业务代码
  GetXMLBuffer(strxmlbuffer,"bizid",&stbiz.bizid);
  // logfile.Write("bizid=%d\n",stbiz.bizid);
  // 用户设备ID
  GetXMLBuffer(strxmlbuffer,"userid",stbiz.userid,50);
  // logfile.Write("userid=%s\n",stbiz.userid);
  GetXMLBuffer(strxmlbuffer,"obtid",stbiz.obtid,10);
  // logfile.Write("obtid=%s\n",stbiz.obtid);
  GetXMLBuffer(strxmlbuffer,"lat",&stbiz.lat);
  // logfile.Write("lat=%lf\n",stbiz.lat);
  GetXMLBuffer(strxmlbuffer,"lon",&stbiz.lon);
  // logfile.Write("lon=%lf\n",stbiz.lon);
  GetXMLBuffer(strxmlbuffer,"height",&stbiz.height);
  // logfile.Write("height=%lf\n",stbiz.height);
  strncpy(stbiz.xmlbuffer,strxmlbuffer,1000);
  return;
}

///////////////////////////////////////////////1.心跳业务
bool biz10000()
{
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  strcpy(strSendBuffer,"ok");
  if (TcpServer.Write(strSendBuffer) == false)
  {
    logfile.Write("biz10000 TcpServer.Write() failed.\n"); return false;
  }
  return true;
}

/////////////////////////////////////2.新用户登录
bool biz10001()
{
CTimer Timer;
///////////////////////////////////////////2.1 插入用户基本信息表T_USERINFO:设备ID和设备类型
  sqlstatement stmt(&conn);
  stmt.prepare("insert into T_USERINFO(userid,downtime,ttytype,keyid) values(:1,sysdate,:2,SEQ_USERINFO.nextval)");
  stmt.bindin(1, stbiz.userid,50);
  stmt.bindin(2,&stbiz.ttytype);
  if (stmt.execute() != 0)
  {
    if (stmt.m_cda.rc!=1) //出现主键冲突不管(之前下载过,表里留下了数据,所以会出现主键冲突)
    {
      logfile.Write("insert T_USERINFO failed.\n%s\n%s\n",stmt.m_cda.message,stmt.m_sql); return false;
    }
  }
  logfile.Write("insert T_USERINFO =%lf\n",Timer.Elapsed());
  
///////////////////////////////////////////////////////2.2 插入用户请求日志表
  if (InsertUSERLOG()==false) return false; // 是模块通用功能,所以写成函数
  logfile.Write("insert T_USERLOG =%lf\n",Timer.Elapsed());
  
//////////////////////////////////////////////////2.3 拿出全国站点参数信息返回给客户端
  char strobtid[6],strobtname[31],strlon[11],strlat[11];
  stmt.prepare("select obtid,obtname,lon,lat from T_OBTCODE where rsts=1 and rownum<=30");
  stmt.bindout(1,strobtid,5);
  stmt.bindout(2,strobtname,30);
  stmt.bindout(3,strlon,10);
  stmt.bindout(4,strlat,10);
  if (stmt.execute() != 0)
  {
    logfile.Write("select T_OBTCODE failed.\n%s\n%s\n",stmt.m_cda.message,stmt.m_sql); return false;
  }
  while (true)
  {
    memset(strobtid,0,sizeof(strobtid)); 
    memset(strobtname,0,sizeof(strobtname));
    memset(strlon,0,sizeof(strlon)); 
    memset(strlat,0,sizeof(strlat));
    memset(strSendBuffer,0,sizeof(strSendBuffer));
    if (stmt.next()!=0) break;
    //获得一条记录后生成一个buffer写过去
    sprintf(strSendBuffer,"<obtid>%s</obtid><obtname>%s</obtname><lon>%s</lon><lat>%s</lat><endl/>",strobtid,strobtname,strlon,strlat);

    if (TcpServer.Write(strSendBuffer) == false)
    {
      logfile.Write("biz10001 TcpServer.Write() failed.\n"); return false;
    }
  }
  logfile.Write("select =%lf\n",Timer.Elapsed());
  // 直到全部记录被处理完,最后发送一个ok
  strcpy(strSendBuffer,"ok");
  if (TcpServer.Write(strSendBuffer) == false)
  {
    logfile.Write("biz10001 TcpServer.Write() failed.\n"); return false;
  }
  return true;
}

/////////////////////////////////3.插入用户请求日志表
bool InsertUSERLOG()
{
  sqlstatement stmt(&conn);
  stmt.prepare("insert into T_USERLOG(logid,userid,atime,bizid,obtid,lon,lat,height,xmlbuffer) values(SEQ_USERLOG.nextval,:1,sysdate,:2,:3,:4,:5,:6,:7)");
  stmt.bindin(1, stbiz.userid,50);
  stmt.bindin(2,&stbiz.bizid);
  stmt.bindin(3, stbiz.obtid,10);
  stmt.bindin(4,&stbiz.lon);
  stmt.bindin(5,&stbiz.lat);
  stmt.bindin(6,&stbiz.height);
  stmt.bindin(7, stbiz.xmlbuffer,10000);
  if (stmt.execute() != 0)
  {
    logfile.Write("insert T_USERLOG failed.\n%s\n%s\n",stmt.m_cda.message,stmt.m_sql); return false;
  }
  return true;
}

////////////////////////4.获取天气实况
bool biz10002()
{
   return true;
}

如下现在只有一个心跳业务,创建同义词好处当shtqapp用户里表满了迁移到shappdata用户表里,代码不用变


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.测试服务端的性能:CTimer

一分钟4万3次请求,则1秒700次请求(测试一次访问请求要多少时间,再算出1秒可有多少次访问请求)

在这里插入图片描述
如下在client.cpp中,记下这个业务从发送请求到接收完成要多长时间
在这里插入图片描述
如下在shtqappserver.cpp中
在这里插入图片描述
用外网地址测试,总的说客户端做一次业务交互(连数据库和收发报文)在1秒中内可以完成
在这里插入图片描述
到底采用长连接还是短链接?如果一秒700次请求,同时在线一分钟则700乘60约近4万次,长连接4万socket连上,4万进程服务器不可接受,所以采用短链接(长短连接由客户端控制:客户端发多个报文中间再发心跳是长连接,短连接发一次请求,连接断开一次)。如下同时运行50个客户端,业务耗时1秒以上,1秒50个没问题。实际业务系统服务器性能至少高于10倍且APP软件服务器可以几台,最简单方法:来一个请求,服务端生成一个进程连一次数据库,这能满足要求
在这里插入图片描述
客户端连接越多,服务端连数据库耗时越长,26:00到26:02服务端总耗时2秒
在这里插入图片描述

3.高性能网络服务开发:多线程,连接池

旧框架中前面4个字节是ascii码即美码


在这里插入图片描述

新框架中前面4字节是整数,写进去不是0010,是二进制的整数。读报文时也是先读一个整数出来再得到后面报文长度。所以不需要宏,报文长度也没限制,整数取多大就取多大


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
//上海天气APP软件服务端主程序shtqappserver.cpp多线程方式
#include "_freecplus.h"
#include "_ooci.h"
// 业务请求
struct st_biz
{
  int  bizid;               // 业务代码
  char userid[51];          // 设备ID
  int  ttytype;             // 用户的设备类型,0-未知;1-IOS;2-Andriod,2-鸿蒙。
  int  usertype;            // 用户分类,0-未知;1-普通用户;2-气象志愿者;3-内部用户。
  double lon;
  double lat;
  double height;
  char   obtid[11];
  char   xmlbuffer[1001];
};
// 把xml解析到参数stbiz结构中
void xmltobiz(char *strxmlbuffer,struct st_biz *stbiz);
CTcpServer TcpServer;
CLogFile   logfile;
// 程序退出时调用的函数
void EXIT(int sig);
// 与客户端通信线程的主函数
void *pth_main(void *arg);
// 心跳业务
bool biz10000(int clientfd);
// 新用户登录业务
bool biz10001(struct st_biz *stbiz,connection *conn,int clientfd);
// 获取天气实况
bool biz10002(struct st_biz *stbiz,connection *conn,int clientfd);
// 插入用户请求日志表
bool InsertUSERLOG(struct st_biz *stbiz,connection *conn);
// 存放客户端已连接的socket的容器
vector<int> vclientfd;
void AddClient(int clientfd);      // 把客户端新的socket加入vclientfd容器中
void RemoveClient(int clientfd);   // 关闭客户端的socket并从vclientfd容器中删除,
// 关闭客户端的socket并从vclientfd容器中删除,
void RemoveClient(int clientfd);

int main(int argc,char *argv[])
{
  if (argc != 3)
  {
    printf("\n");
    printf("Using:/htidc/shtqapp1/bin/shtqappserver logfilename port\n");

    printf("Example:/htidc/shtqapp1/bin/shtqappserver /log/shtqapp/shtqappserver.log 5015\n\n");
    printf("本程序是上海天气APP软件的服务端。\n");
    printf("logfilename 日志文件名。\n");
    printf("port 用于传输文件的TCP端口。\n");
    return -1;
  }
  // 关闭全部的信号和输入输出
  // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程
  // 但请不要用 "kill -9 +进程号" 强行终止
  CloseIOAndSignal(); signal(SIGINT,EXIT); signal(SIGTERM,EXIT);
  // 打开程序运行日志,这是一个多进程程序,日志不能自动切换
  if (logfile.Open(argv[1],"a+",false) == false)
  {
    printf("logfile.Open(%s) failed.\n",argv[1]); return -1;
  }
  logfile.Write("shtqappserver started(%s).\n",argv[2]);
  if (TcpServer.InitServer(atoi(argv[2])) == false)
  {
    logfile.Write("TcpServer.InitServer(%s) failed.\n",argv[2]); exit(-1);
  }
  // 保存服务端的listenfd到vclientfd
  AddClient(TcpServer.m_listenfd);
  while (true)
  {
    // 等待客户端的连接
    if (TcpServer.Accept() == false)
    {
      logfile.Write("TcpServer.Accept() failed.\n"); continue;
    }
    pthread_t pthid;   // 创建一线程,与新连接上来的客户端通信
    if (pthread_create(&pthid,NULL,pth_main,(void*)(long)TcpServer.m_connfd)!=0)
    {
      logfile.Write("创建线程失败,程序退出。n"); close(TcpServer.m_connfd); EXIT(-1);
    }
    logfile.Write("%s is connected.\n",TcpServer.GetIP());
    // 保存每个客户端的socket到vclientfd
    AddClient(TcpServer.m_connfd);
  }
  return 0;
}

// 退出时调用的函数
void EXIT(int sig)
{
  signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
  if (sig>0) signal(sig,SIG_IGN);
  logfile.Write("tcpfileserver1 exit,sig=%d...\n",sig);
  // 关闭vclientfd容器中全部的socket
  for (int ii=0;ii<vclientfd.size();ii++)
  {
    close(vclientfd[ii]);
  }
  exit(0);
}

// 与客户端通信线程的主函数
void *pth_main(void *arg)
{
  int clientfd=(long) arg; // arg参数为新客户端的socket。
  pthread_detach(pthread_self());
  struct st_biz stbiz;
  connection conn; //不能定义为全局变量
  int  ibuflen=0;
  char strRecvBuffer[1024]; //接收报文的缓冲区,不能定义为全局变量
  while (true)
  {
    memset(strRecvBuffer,0,sizeof(strRecvBuffer));
    // 接收客户端的业务请求报文,如果返回false,认为是客户端退出或网络原因,不写错误日志
    if (TcpRead(clientfd,strRecvBuffer,&ibuflen,50) == false)
    {
      // logfile.Write("TcpRead() failed.\n"); 
      break;
    }
    logfile.Write("strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx
    // 把参数解析出来
    xmltobiz(strRecvBuffer,&stbiz);
    if (stbiz.bizid==10000)    // 心跳报文
    {
      if (biz10000(clientfd)==true) continue;
      else break;
    }
    CTimer Timer;
    if (conn.m_state==0)
    {
      // 连接数据库
      if (conn.connecttodb("shtqapp/pwdidc@snorcl11g_198","Simplified Chinese_China.ZHS16GBK",true)!=0)
      {
        logfile.Write("conn.connettodb() failed.\n"); break;
      }
      logfile.Write("conn=%lf\n",Timer.Elapsed());
    }
    // 新用户登录 
    if (stbiz.bizid==10001)    
    {
      if (biz10001(&stbiz,&conn,clientfd)==true) continue;
      else break;
    }
    // 获取天气实况
    if (stbiz.bizid==10002)    
    {
      if (biz10002(&stbiz,&conn,clientfd)==true) continue;
      else break;
    }
    // 体力活
    logfile.Write("非法报文%s\n",strRecvBuffer); break;
  }
  RemoveClient(clientfd);
  pthread_exit(0);
}

// 把xml解析到参数starg结构中
void xmltobiz(char *strxmlbuffer,struct st_biz *stbiz)
{
  memset(stbiz,0,sizeof(struct st_biz));
  // 业务代码
  GetXMLBuffer(strxmlbuffer,"bizid",&stbiz->bizid);
  // logfile.Write("bizid=%d\n",stbiz->bizid);
  // 用户设备ID
  GetXMLBuffer(strxmlbuffer,"userid",stbiz->userid,50);
  // logfile.Write("userid=%s\n",stbiz->userid);
  GetXMLBuffer(strxmlbuffer,"obtid",stbiz->obtid,10);
  // logfile.Write("obtid=%s\n",stbiz->obtid);
  GetXMLBuffer(strxmlbuffer,"lat",&stbiz->lat);
  // logfile.Write("lat=%lf\n",stbiz->lat);
  GetXMLBuffer(strxmlbuffer,"lon",&stbiz->lon);
  // logfile.Write("lon=%lf\n",stbiz->lon);
  GetXMLBuffer(strxmlbuffer,"height",&stbiz->height);
  // logfile.Write("height=%lf\n",stbiz->height);  
  strncpy(stbiz->xmlbuffer,strxmlbuffer,1000);
  return;
}

////////////////////////////////////////////////////////////////1.心跳业务
bool biz10000(int clientfd)
{
  char strSendBuffer[1024]; // 发送报文的缓冲区
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  strcpy(strSendBuffer,"ok");
  if (TcpWrite(clientfd,strSendBuffer) == false)
  {
    logfile.Write("biz10000 TcpWrite() failed.\n"); return false;
  }
  return true;
}

////////////////////////////////////////////////////////////////2.新用户登录
bool biz10001(struct st_biz *stbiz,connection *conn,int clientfd)
{
  char strSendBuffer[1024]; // 发送报文的缓冲区
CTimer Timer;
  // 插入用户基本信息表T_USERINFO
  sqlstatement stmt(conn);
  stmt.prepare("insert into T_USERINFO(userid,downtime,ttytype,keyid) values(:1,sysdate,:2,SEQ_USERINFO.nextval)");
  stmt.bindin(1, stbiz->userid,50);
  stmt.bindin(2,&stbiz->ttytype);
  if (stmt.execute() != 0)
  {
    if (stmt.m_cda.rc!=1)
    {
      logfile.Write("insert T_USERINFO failed.\n%s\n%s\n",stmt.m_cda.message,stmt.m_sql); return false;
    }
  }
  logfile.Write("insert T_USERINFO =%lf\n",Timer.Elapsed());
  // 插入用户请求日志表
  if (InsertUSERLOG(stbiz,conn)==false) return false;
  logfile.Write("insert T_USERLOG =%lf\n",Timer.Elapsed());

  char strobtid[6],strobtname[31],strlon[11],strlat[11];
  stmt.prepare("select obtid,obtname,lon,lat from T_OBTCODE where rsts=1 and rownum<=30");
  stmt.bindout(1,strobtid,5);
  stmt.bindout(2,strobtname,30);
  stmt.bindout(3,strlon,10);
  stmt.bindout(4,strlat,10);
  if (stmt.execute() != 0)
  {
    logfile.Write("select T_OBTCODE failed.\n%s\n%s\n",stmt.m_cda.message,stmt.m_sql); return false;
  }
  while (true)
  {
    memset(strobtid,0,sizeof(strobtid)); 
    memset(strobtname,0,sizeof(strobtname));
    memset(strlon,0,sizeof(strlon)); 
    memset(strlat,0,sizeof(strlat));
    memset(strSendBuffer,0,sizeof(strSendBuffer));
    if (stmt.next()!=0) break;
    sprintf(strSendBuffer,"<obtid>%s</obtid><obtname>%s</obtname><lon>%s</lon><lat>%s</lat><endl/>",strobtid,strobtname,strlon,strlat);
    if (TcpWrite(clientfd,strSendBuffer) == false)
    {
      logfile.Write("biz10001 TcpWrite() failed.\n"); return false;
    }
  }
  logfile.Write("select =%lf\n",Timer.Elapsed());
  // 最后发送一个ok
  strcpy(strSendBuffer,"ok");
  if (TcpWrite(clientfd,strSendBuffer) == false)
  {
    logfile.Write("biz10001 TcpWrite() failed.\n"); return false;
  }
  return true;
}

////////////////////////////////////////////////////////////////3.插入用户请求日志表
bool InsertUSERLOG(struct st_biz *stbiz,connection *conn)
{
  sqlstatement stmt(conn);
  stmt.prepare("insert into T_USERLOG(logid,userid,atime,bizid,obtid,lon,lat,height,xmlbuffer) values(SEQ_USERLOG.nextval,:1,sysdate,:2,:3,:4,:5,:6,:7)");
  stmt.bindin(1, stbiz->userid,50);
  stmt.bindin(2,&stbiz->bizid);
  stmt.bindin(3, stbiz->obtid,10);
  stmt.bindin(4,&stbiz->lon);
  stmt.bindin(5,&stbiz->lat);
  stmt.bindin(6,&stbiz->height);
  stmt.bindin(7, stbiz->xmlbuffer,10000);
  if (stmt.execute() != 0)
  {
    logfile.Write("insert T_USERLOG failed.\n%s\n%s\n",stmt.m_cda.message,stmt.m_sql); return false;
  }
  return true;
}

//////////////////////////////////////////////////////////////4.获取天气实况
bool biz10002(struct st_biz *stbiz,connection *conn,int clientfd)
{
   return true;
}

///////////////////////////////////////////////5.把客户端新的socket加入vclientfd容器中
void AddClient(int clientfd)
{
  vclientfd.push_back(clientfd);
}

///////////////////////////////////////6.关闭客户端的socket并从vclientfd容器中删除,
void RemoveClient(int clientfd)
{
  for (int ii=0;ii<vclientfd.size();ii++)
  {
    if (vclientfd[ii]==clientfd) 
    { close(clientfd); vclientfd.erase(vclientfd.begin()+ii); return; }
  }
}
在这里插入图片描述
多线程每启一个线程都要连数据库不划算,所以采用连接池。程序启动时初始化用一数组创建10个数据库连接connection,每次想要使用数据库连接就会从10个已创建好的connection看看哪个没锁就拿1个过来用。数据库连接池的设计可用一个参数去控制连接池的总大小,比如这连接池里有10个connection连接就需要10把锁。在sqlstatement每次想使用数据库连接时就会从10个已创建好的connection看看哪个没锁就拿1个过来用
//上海天气APP软件服务端主程序。shtqappserver1.cpp多线程方式,采用连接池。
#include "_freecplus.h"
#include "_ooci.h"
#define MAXCONNS 10  // 数据库连接池的大小。
pthread_mutex_t mutex[MAXCONNS];  // 锁数组。
connection conns[MAXCONNS];  // 数据库连接数组。
bool initconns();   // 初始数据库连接池。
connection *getconns();  // 从连接池中获取一个数据库连接。
bool freeconns(connection *in_conn);  // 释放数据库连接。
// 业务请求
struct st_biz
{
  int  bizid;               // 业务代码
  char userid[51];          // 设备ID
  int  ttytype;             // 用户的设备类型,0-未知;1-IOS;2-Andriod,2-鸿蒙。
  int  usertype;            // 用户分类,0-未知;1-普通用户;2-气象志愿者;3-内部用户。
  double lon;
  double lat;
  double height;
  char   obtid[11];
  char   xmlbuffer[1001];
};

// 把xml解析到参数stbiz结构中
void xmltobiz(char *strxmlbuffer,struct st_biz *stbiz);
CTcpServer TcpServer;
CLogFile   logfile;
// 程序退出时调用的函数
void EXIT(int sig);
// 与客户端通信线程的主函数
void *pth_main(void *arg);
// 心跳业务
bool biz10000(int clientfd);
// 新用户登录业务
bool biz10001(struct st_biz *stbiz,int clientfd);
// 获取天气实况
bool biz10002(struct st_biz *stbiz,int clientfd);
// 插入用户请求日志表
bool InsertUSERLOG(struct st_biz *stbiz,connection *conn);
// 存放客户端已连接的socket的容器
vector<int> vclientfd;
void AddClient(int clientfd);      // 把客户端新的socket加入vclientfd容器中
void RemoveClient(int clientfd);   // 关闭客户端的socket并从vclientfd容器中删除,
// 关闭客户端的socket并从vclientfd容器中删除,
void RemoveClient(int clientfd);

int main(int argc,char *argv[])
{
  if (argc != 3)
  {
    printf("\n");
    printf("Using:/htidc/shtqapp1/bin/shtqappserver1 logfilename port\n");

    printf("Example:/htidc/shtqapp1/bin/shtqappserver1 /log/shtqapp/shtqappserver1.log 5015\n\n");
    printf("本程序是上海天气APP软件的服务端。\n");
    printf("logfilename 日志文件名。\n");
    printf("port 用于传输文件的TCP端口。\n");
    return -1;
  }
  // 关闭全部的信号和输入输出
  // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程
  // 但请不要用 "kill -9 +进程号" 强行终止
  CloseIOAndSignal(); signal(SIGINT,EXIT); signal(SIGTERM,EXIT);
  // 打开程序运行日志,这是一个多进程程序,日志不能自动切换
  if (logfile.Open(argv[1],"a+",false) == false)
  {
    printf("logfile.Open(%s) failed.\n",argv[1]); return -1;
  }
  logfile.Write("shtqappserver started(%s).\n",argv[2]);
  if (TcpServer.InitServer(atoi(argv[2])) == false)
  {
    logfile.Write("TcpServer.InitServer(%s) failed.\n",argv[2]); EXIT(-1);
  }

  // 保存服务端的listenfd到vclientfd
  AddClient(TcpServer.m_listenfd);
  if (initconns()==false)  // 初始化数据库连接池。
  {
    logfile.Write("initconns() failed.\n"); EXIT(-1);
  }

  while (true)
  {
    // 等待客户端的连接
    if (TcpServer.Accept() == false)
    {
      logfile.Write("TcpServer.Accept() failed.\n"); continue;
    }
    pthread_t pthid;   // 创建一线程,与新连接上来的客户端通信
    if (pthread_create(&pthid,NULL,pth_main,(void*)(long)TcpServer.m_connfd)!=0)
    {
      logfile.Write("创建线程失败,程序退出。n"); close(TcpServer.m_connfd); EXIT(-1);
    }
    logfile.Write("%s is connected.\n",TcpServer.GetIP());
    // 保存每个客户端的socket到vclientfd
    AddClient(TcpServer.m_connfd);
  }
  return 0;
}

// 退出时调用的函数
void EXIT(int sig)
{
  signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
  if (sig>0) signal(sig,SIG_IGN);
  logfile.Write("tcpfileserver1 exit,sig=%d...\n",sig);
  // 关闭vclientfd容器中全部的socket
  for (int ii=0;ii<vclientfd.size();ii++)
  {
    close(vclientfd[ii]);
  }
  for (int ii=0;ii<MAXCONNS;ii++)
  {
    logfile.Write("disconnect and pthread_mutex_destroy.\n");
    conns[ii].disconnect();
    pthread_mutex_destroy(&mutex[ii]);
  }
  exit(0);
}

// 与客户端通信线程的主函数
void *pth_main(void *arg)
{
  int clientfd=(long) arg; // arg参数为新客户端的socket。
  pthread_detach(pthread_self());
  struct st_biz stbiz;
  int  ibuflen=0;
  char strRecvBuffer[1024]; // 接收报文的缓冲区
  while (true)
  {
    memset(strRecvBuffer,0,sizeof(strRecvBuffer));
    // 接收客户端的业务请求报文,如果返回false,认为是客户端退出或网络原因,不写错误日志
    if (TcpRead(clientfd,strRecvBuffer,&ibuflen,50) == false)
    {
      // logfile.Write("TcpRead() failed.\n"); 
      break;
    }
    logfile.Write("strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx
    // 把参数解析出来
    xmltobiz(strRecvBuffer,&stbiz);
    if (stbiz.bizid==10000)    // 心跳报文
    {
      if (biz10000(clientfd)==true) continue;
      else break;
    }
    // 新用户登录 
    if (stbiz.bizid==10001)    
    {
      if (biz10001(&stbiz,clientfd)==true) continue;
      else break;
    }
    // 获取天气实况
    if (stbiz.bizid==10002)    
    {
      if (biz10002(&stbiz,clientfd)==true) continue;
      else break;
    }
    // 体力活
    logfile.Write("非法报文%s\n",strRecvBuffer); break;
  }
  RemoveClient(clientfd);
  pthread_exit(0);
}

// 把xml解析到参数starg结构中
void xmltobiz(char *strxmlbuffer,struct st_biz *stbiz)
{
  memset(stbiz,0,sizeof(struct st_biz));
  // 业务代码
  GetXMLBuffer(strxmlbuffer,"bizid",&stbiz->bizid);
  // logfile.Write("bizid=%d\n",stbiz->bizid);
  // 用户设备ID
  GetXMLBuffer(strxmlbuffer,"userid",stbiz->userid,50);
  // logfile.Write("userid=%s\n",stbiz->userid);
  GetXMLBuffer(strxmlbuffer,"obtid",stbiz->obtid,10);
  // logfile.Write("obtid=%s\n",stbiz->obtid);
  GetXMLBuffer(strxmlbuffer,"lat",&stbiz->lat);
  // logfile.Write("lat=%lf\n",stbiz->lat);
  GetXMLBuffer(strxmlbuffer,"lon",&stbiz->lon);
  // logfile.Write("lon=%lf\n",stbiz->lon);
  GetXMLBuffer(strxmlbuffer,"height",&stbiz->height);
  // logfile.Write("height=%lf\n",stbiz->height);
  strncpy(stbiz->xmlbuffer,strxmlbuffer,1000);
  return;
}

/////////////////////////////////////////////////////////////1.心跳业务
bool biz10000(int clientfd)
{
  char strSendBuffer[1024]; // 发送报文的缓冲区
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  strcpy(strSendBuffer,"ok");
  if (TcpWrite(clientfd,strSendBuffer) == false)
  {
    logfile.Write("biz10000 TcpWrite() failed.\n"); return false;
  }
  return true;
}

////////////////////////////////////////////////////////2.新用户登录
bool biz10001(struct st_biz *stbiz,int clientfd)
{
  CTimer Timer;
  char strSendBuffer[1024]; // 发送报文的缓冲区  
  connection *conn=getconns();  // 获取一个数据库连接。
  // 插入用户基本信息表T_USERINFO
  sqlstatement stmt(conn);
  stmt.prepare("insert into T_USERINFO(userid,downtime,ttytype,keyid) values(:1,sysdate,:2,SEQ_USERINFO.nextval)");
  stmt.bindin(1, stbiz->userid,50);
  stmt.bindin(2,&stbiz->ttytype);
  if (stmt.execute() != 0)
  {
    if (stmt.m_cda.rc!=1)
    {
      logfile.Write("insert T_USERINFO failed.\n%s\n%s\n",stmt.m_cda.message,stmt.m_sql); freeconns(conn); return false;
    }
  }
  logfile.Write("insert T_USERINFO =%lf\n",Timer.Elapsed());
  // 插入用户请求日志表
  if (InsertUSERLOG(stbiz,conn)==false) { freeconns(conn); return false; }
  logfile.Write("insert T_USERLOG =%lf\n",Timer.Elapsed());
  char strobtid[6],strobtname[31],strlon[11],strlat[11];
  stmt.prepare("select obtid,obtname,lon,lat from T_OBTCODE where rsts=1 and rownum<=30");
  stmt.bindout(1,strobtid,5);
  stmt.bindout(2,strobtname,30);
  stmt.bindout(3,strlon,10);
  stmt.bindout(4,strlat,10);
  if (stmt.execute() != 0)
  {
    logfile.Write("select T_OBTCODE failed.\n%s\n%s\n",stmt.m_cda.message,stmt.m_sql); freeconns(conn); return false;
  }
  while (true)
  {
    memset(strobtid,0,sizeof(strobtid)); 
    memset(strobtname,0,sizeof(strobtname));
    memset(strlon,0,sizeof(strlon)); 
    memset(strlat,0,sizeof(strlat));
    memset(strSendBuffer,0,sizeof(strSendBuffer));

    if (stmt.next()!=0) break;
    sprintf(strSendBuffer,"<obtid>%s</obtid><obtname>%s</obtname><lon>%s</lon><lat>%s</lat><endl/>",strobtid,strobtname,strlon,strlat);

    if (TcpWrite(clientfd,strSendBuffer) == false)
    {
      logfile.Write("biz10001 TcpWrite() failed.\n"); freeconns(conn); return false;
    }
  }
  logfile.Write("select =%lf\n",Timer.Elapsed());
  // 最后发送一个ok
  strcpy(strSendBuffer,"ok");
  if (TcpWrite(clientfd,strSendBuffer) == false)
  {
    logfile.Write("biz10001 TcpWrite() failed.\n"); freeconns(conn); return false;
  }
  freeconns(conn);
  return true;
}

///////////////////////////////////////////////////3.插入用户请求日志表
bool InsertUSERLOG(struct st_biz *stbiz,connection *conn)
{
  sqlstatement stmt(conn);
  stmt.prepare("insert into T_USERLOG(logid,userid,atime,bizid,obtid,lon,lat,height,xmlbuffer) values(SEQ_USERLOG.nextval,:1,sysdate,:2,:3,:4,:5,:6,:7)");
  stmt.bindin(1, stbiz->userid,50);
  stmt.bindin(2,&stbiz->bizid);
  stmt.bindin(3, stbiz->obtid,10);
  stmt.bindin(4,&stbiz->lon);
  stmt.bindin(5,&stbiz->lat);
  stmt.bindin(6,&stbiz->height);
  stmt.bindin(7, stbiz->xmlbuffer,10000);
  if (stmt.execute() != 0)
  {
    logfile.Write("insert T_USERLOG failed.\n%s\n%s\n",stmt.m_cda.message,stmt.m_sql); return false;
  }
  return true;
}

//////////////////////////////////////////////////4.获取天气实况
bool biz10002(struct st_biz *stbiz,int clientfd)
{
   return true;
}

////////////////////////////////////////5.把客户端新的socket加入vclientfd容器中
void AddClient(int clientfd)
{
  vclientfd.push_back(clientfd);
}

//////////////////////////////////6.关闭客户端的socket并从vclientfd容器中删除,
void RemoveClient(int clientfd)
{
  for (int ii=0;ii<vclientfd.size();ii++)
  {
    if (vclientfd[ii]==clientfd) 
    { close(clientfd); vclientfd.erase(vclientfd.begin()+ii); return; }
  }
}

//////////////////////////////////7.初始数据库连接池:连接好数据库,初始化锁
bool initconns()  
{
  for (int ii=0;ii<MAXCONNS;ii++)
  {
    logfile.Write("%d,connecttodb and pthread_mutex_init.\n",ii);
    // 连接数据库
    if (conns[ii].connecttodb("shtqapp/pwdidc@snorcl11g_198","Simplified Chinese_China.ZHS16GBK",true)!=0)
    {
      logfile.Write("conns[%d].connettodb() failed.\n",ii); return false;
    }
    pthread_mutex_init(&mutex[ii],0); // 创建锁
  }
  return true;
}

///////////////////////////////8.获得连接池
connection *getconns()
{
  // for (int jj=0;jj<1000;jj++)
  while (true)
  {
    for (int ii=0;ii<MAXCONNS;ii++)
    {
      if (pthread_mutex_trylock(&mutex[ii])==0) 
      {
        // logfile.Write("jj=%d,ii=%d\n",jj,ii);
        logfile.Write("get conns is %d.\n",ii);
        return &conns[ii]; 
      }
    }  
    usleep(10000);
  }
}

///////////////////////////////9.释放连接池
bool freeconns(connection *in_conn)
{
  for (int ii=0;ii<MAXCONNS;ii++)
  {
    if (in_conn==&conns[ii]) 
    {
      logfile.Write("free conn %d.\n",ii);
      pthread_mutex_unlock(&mutex[ii]); in_conn=0; return true;
    }
  }
  return false; //理论上不会来到这里,除非连接池搞乱了
}

每次要用数据库就从连接池里拿一个,如下是错误的,函数里传入的参数应该用指针的指针


在这里插入图片描述
在这里插入图片描述

心跳报文业务不需要连接池


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
多次./client,如下查看释放的
在这里插入图片描述

如下测100个客户端,系统负载


在这里插入图片描述
sh client.sh(50个./client&),如下采用连接池1秒内,不采用连接池只多线程耗时1秒往上:客户端起50个进程,服务端起50个线程再连数据库,数据库也50个进程,资源消耗大
在这里插入图片描述
如上和浏览器的web服务器一样后面有个数据库连接池,客户发起一次请求短链接,后台有个线程去响应,这个线程到数据库连接池里拿一个连接,Apacheweb服务器就采用这种机制,现在1核2G内存运行数据库还有服务程序,资源紧张。物理服务器一个不够用多个外面再用nginx,就像redis用nginx提供api

相关文章

网友评论

      本文标题:【C/C++】项目_10_APP软件服务端(client.cpp

      本文链接:https://www.haomeiwen.com/subject/lvsjhktx.html