【c++1】函数重载,类和对象,引用,string类,vector容器,类继承和多态,/socket


1.函数重载:writetofile(),C++true和false,C0和非0

C++动态内存分配:在C语言中,动态分配内存用malloc()函数,释放内存用free()函数。C++中new和delete。C++函数重载:C中不允许函数同名如下:
在这里插入图片描述
在这里插入图片描述
以上为C写法,下面为C++函数重载写法。函数重载规则:1.函数名必须同+2.参数列表必须不同。C++是如何做到函数重载的:C++代码在编译时会根据参数列表对函数进行重命名。
在这里插入图片描述

2.类和对象:vprintf

在这里插入图片描述
上面完整,下面两行中下行是上行改进,效果一样,但没有涉及类和对象。
在这里插入图片描述
上面完整,下面结构体升级为类。
在这里插入图片描述
下面为三种show函数重载实现,如下字符串理论上可定义为char name[10],但在函数里字符串也只能传地址,所以只能定义为char * name。char name不行,char类型是单个字符,调用时直接给字符串值。
在这里插入图片描述
下面为三种Show调用。
在这里插入图片描述

2.1 构造函数:对成员变量初始化

在这里插入图片描述
CFile是类,CFile()是函数。
在这里插入图片描述
如下两个构造函数(该类对象被创建时,编译系统对象分配内存空间,并自动调用该构造函数,由构造函数完成成员的初始化工作),属于成员函数。
在这里插入图片描述

2.2 析构函数:一个类只有一个,不允许被重载

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

3.引用:C中&取地址,C++中&引用。引用就像起别名,typedef,宏define。对引用的操作与对变量直接操作一样

引用的声明方法:类型标识符 &引用名=目标变量名;如int a; int &ra=a; 定义了引用ra,它是变量a的引用即别名。引用可以用const修饰,表示只读,用这种方式声明的引用,不能通过引用对目标变量的值进行修改。
在这里插入图片描述
在这里插入图片描述

4.string类:string str,str=,str.c_str()

C中以0结尾的字符数组表示字符串(定义后大小不可变),C++中string随着存放字符长度自动伸缩,不用担心内存溢出。string类是一个模板类,位于std命名空间,如果不加using namespace std;就要用std::string str
在这里插入图片描述
string特性描述函数:int size()返回当前字符串大小int length()返回当前字符串的长度void clear()清空字符串。string本质是一个类,通过动态分配内存实现对字符串的存储,string对象用于存放字符的内存地址是变化的。也就是地址存放的下就不再重新分配,存放不下就重新分配地址。

5.vector容器:vector与string类一样属于STL

在这里插入图片描述
容器的使用:1.存放整数
在这里插入图片描述
访问容器中元素可以像数组形式一样。
在这里插入图片描述
在这里插入图片描述
2.存放字符串
在这里插入图片描述
3.存放结构体4.存放类:存放字符串中,string就是类。
在这里插入图片描述
vector其他成员函数:1.定位的函数
在这里插入图片描述
2.增加元素的函数
在这里插入图片描述
3.删除元素的函数
在这里插入图片描述
4.判断容器的大小
bool empty():判断容器是否为空
int size():返回容器中元素的个数
5.作业题:封装随机数
在这里插入图片描述

//此程序用于生成一组随机数, 指定数组范围和是否重复
#include"_public.h"

class CRand
{
public:
	CRand();
	~CRand();
	vector <int> m_val;  //m_val容器	
	bool checkexit(const int aryyval, const int aryysize); // 用于检查是否为重复数据,aryyval为重复的值,这函数不单用,用于Rand成员函数里
	void Rand(const int minvalue,const int maxvalue,bool brep=true, const int nog=5); //brep为是否允许重复; 默认为允许重复,nog指定生成多少个随机数
};

//111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
void CRand::Rand(const int minvalue,const int maxvalue,bool brep,const int nog)
{
	int len = maxvalue-minvalue;
	int ii=0, itmp=0, jtmp=0;  // ii生成第几个,jtmp实际生成共多少个,itmp生成的值
	m_val.clear();
 
	if(brep==true)    // 允许重复
	{
		jtmp = nog;
		for(ii=0;ii<jtmp;ii++)
		{
			itmp = rand()%(len+1)+minvalue; // (0~len)+minvalue,itmp就是min~max之间的值,不是len长度
			m_val.push_back(itmp);    
		}
		return; //return是函数直接返回, 也就是结束该函数。
	//要跳出循环用break, if代码段是不能用break跳出的, 在一个函数内任意位置调用return, 直接退出Rand函数,下面代码不执行。
	}

	jtmp = nog;    // 以下为不允许重复 ,因为没进入if(brep==true)
	if (nog>len) jtmp = len + 1;  // 比如5-1=4,但1到5可以生成5个,所以如果nog大于len的话就取len+1个,前提不允许重复。
	while(1)   
	{
		if (jtmp == m_val.size()) break;  //生成满了跳出循环
		itmp = rand()%(len+1)+minvalue;
		if (ii==0)  // 生成第一个不用管checkexit重不重复
		{
			m_val.push_back(itmp);
			ii++;
			continue;
		} 

		if (checkexit(itmp,ii) == false) continue;  // checkexit为false则不允许重复
		m_val.push_back(itmp); ii++;
	}
	return;
}

//11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
bool CRand::checkexit(const int aryyval, const int aryysize) // aryyval重复的值,aryysize允许多少个重复
{
	for (int ii=0; ii<aryysize; ii++)
	{
		if (aryyval == m_val[ii]) return false;
	}
	return true;
}

CRand::~CRand()
{
	m_val.clear();
}

CRand::CRand()
{
	struct timeval begin;
	gettimeofday(&begin, 0);
	srand(begin.tv_usec);
}

//1111111111111111111111111111111111111111111111111111111111111111111111111111111111
int main()   //如何用CRand这个类
{
	CRand CrtRand;
	CrtRand.Rand(0, 10, false);   // 若false为true允许重复,不管范围多少取nog个
	for(int ii=0;ii<CrtRand.m_val.size();ii++)
	{
		printf("%d\n",CrtRand.m_val[ii]);
	}	
	return 0;
}

6.类继承和多态:class派生类名:public基类名

在这里插入图片描述
如下子类可直接用父类属性和方法。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
类多态:子类必重写父类纯虚函数
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.socket:send/recv

在这里插入图片描述
服务端:
在这里插入图片描述
在这里插入图片描述
客户端:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
1.send函数。
在这里插入图片描述
在这里插入图片描述
2.recv函数。
在这里插入图片描述
在这里插入图片描述
1.socket函数
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.1 简单文件传输:CTcpClient,CTcpServer

// 本程序演示采用CTcpClient类,实现socket通讯的客户端和文件传输,demo13.cpp
#include "_public.h"
bool SendFile(int sockfd,char *filename,int filesize); //把文件的内容发送给服务端
int main(int argc,char *argv[])
{
  if (argc != 4)
  {
    printf("\n");
    printf("Using:./demo13 ip port filename\n\n");
    printf("Example:./demo13 118.89.50.198 5010 test1.jpg\n\n");
    printf("本程序演示采用CTcpClient类,实现socket通讯的客户端和文件传输。\n\n");
    return -1;
  } 
  
  if (access(argv[3],R_OK) != 0)  //判断文件是否存
  {
    printf("file %s not exist.\n",argv[3]); return -1;
  }
  int uFileSize=0;
  char strMTime[20],strRecvBuffer[1024],strSendBuffer[1024];
  
  memset(strMTime,0,sizeof(strMTime)); //获取文件的时间和大小
  FileMTime(argv[3],strMTime);  
  uFileSize=FileSize(argv[3]); //获取文件的大小

  // 把文件的信息封装成一个xml报文,发送给服务端
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  snprintf(strSendBuffer,100,"<filename>%s</filename><mtime>%s</mtime><size>%lu</size>",argv[3],strMTime,uFileSize);
  CTcpClient TcpClient;
  
//1111111111111111111111111111111111111111111111111111.向服务器发起连接
  if (TcpClient.ConnectToServer(argv[1],atoi(argv[2])) == false)
  {
    printf("TcpClient.ConnectToServer(%s,%d) failed.\n",argv[1],atoi(argv[2])); return -1;
  }

//1111111111111111111111111111111111111111112.把文件信息的xml发送给服务端,并没有接收服务端回应,没必要,减少tcp交互次数
  if (TcpClient.Write(strSendBuffer)==false)
  {
    printf("TcpClient.Write() failed.\n"); return -1;
  }
  printf("send xml:%s\n",strSendBuffer);
  printf("send file ...");

//111111111111111111111111111111111111111111111111111113.把文件的内容发送给服务端
  if (SendFile(TcpClient.m_sockfd,argv[3],uFileSize)==false)
  {
    printf("SendFile(%s) failed.\n",argv[3]); return -1;
  }  
  memset(strRecvBuffer,0,sizeof(strRecvBuffer));
  
//1111111111111111111111111111111111111111111111111114.接收服务端返回的回应报文
  if (TcpClient.Read(strRecvBuffer)==false)
  {
    printf("TcpClient.Read() failed.\n"); return -1;
  }
  if (strcmp(strRecvBuffer,"ok")==0)
    printf("ok.\n");
  else
    printf("failed.\n");
  return 0;
}

//111111111111111111111111111111111111111111111111113.把文件的内容发送给服务端
bool SendFile(int sockfd,char *filename,int filesize)
{
  int  bytes=0;
  int  total_bytes=0;
  int  onread=0;
  char buffer[1000];
  FILE *fp=NULL;
  if ( (fp=fopen(filename,"rb")) == NULL ) 
  {
    printf("fopen(%s) failed.\n",filename); return false;
  }
  while (true)
  {
    memset(buffer,0,sizeof(buffer));

    if ((filesize-total_bytes) > 1000) onread=1000; //一次读1000个字节
    else onread=filesize-total_bytes;
    bytes=fread(buffer,1,onread,fp); 
    if (bytes > 0)
    {
      if (Writen(sockfd,buffer,bytes) == false)
      {
        printf("Writen() failed.\n"); fclose(fp); fp=NULL; return false;
      }
    }
    total_bytes = total_bytes + bytes;
    if ((int)total_bytes == filesize) break;
  }
  fclose(fp);
  return true;
}
// 本程序演示采用CTcpServer类,实现socket通讯的服务端和文件传输,demo14.cpp
#include "_public.h"
bool RecvFile(char *strRecvBuffer,int sockfd,char *strfilename); //接收文件的内容
int main(int argc,char *argv[])
{
  if (argc != 3)
  {
    printf("\n");
    printf("Using:./demo14 port filename\n\n");
    printf("Example:./demo14 5010 test2.jpg\n\n"); //test2.jpg重新命名
    printf("本程序演示采用CTcpServer类,实现socket通讯的服务端和文件传输。\n\n");
    return -1;
  }

  CTcpServer TcpServer;
//1111111111111111111111111111111111111111111111111111.服务端初始化
  if (TcpServer.InitServer(atoi(argv[1])) == false)
  {
    printf("TcpServer.InitServer(%s) failed.\n",argv[1]); return -1;
  }

//1111111111111111111111111111111111111111111111111112.等待客户端的连接
  if (TcpServer.Accept() == false)
  {
    printf("TcpServer.Accept() failed.\n"); return -1;
  }
  
//11111111111111111111111111111111111111111113.读取客户端的报文,等时间是20秒
  char strRecvBuffer[1024],strSendBuffer[1024];
  memset(strRecvBuffer,0,sizeof(strRecvBuffer));
  if (TcpServer.Read(strRecvBuffer,20)==false) 
  {
    printf("TcpServer.Read() failed.\n"); return -1;
  }
  printf("recv:%s\n",strRecvBuffer);
  printf("recv file ...");

//111111111111111111111111111111111111111111114.接收文件的内容
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  if (RecvFile(strRecvBuffer,TcpServer.m_connfd,argv[2])==true)
  {
    strcpy(strSendBuffer,"ok");
    printf("ok.\n");
  }
  else
  {
    strcpy(strSendBuffer,"failed");
    printf("failed.\n");
  }

//1111111111111111111111111111111111111111111111111115.接收ok后,向客户端返回响应内容
  if (TcpServer.Write(strSendBuffer)==false) 
  {
    printf("TcpServer.Write() failed.\n"); return -1;
  }
  printf("send:%s\n",strSendBuffer);
  return 0;
}

//1111111111111111111111111111111111111111111111111114.接收文件的内容
bool RecvFile(char *strRecvBuffer,int sockfd,char *strfilename)
{
  int  ufilesize=0;
  char strmtime[20]; 
  memset(strmtime,0,sizeof(strmtime));
  // 获取待接收的文件的时间和大小
  GetXMLBuffer(strRecvBuffer,"mtime",strmtime);
  GetXMLBuffer(strRecvBuffer,"size",&ufilesize);
  
  FILE *fp=NULL;
  if ( (fp=fopen(strfilename,"wb")) ==NULL)
  {
    printf("create %s failed.\n",strfilename); return false;
  }

  int  total_bytes=0;
  int  onread=0;
  char buffer[1000];
  while (true)
  {
    memset(buffer,0,sizeof(buffer));
    if ((ufilesize-total_bytes) > 1000) onread=1000; //根据文件大小知道文件接下来读取多少内容
    else onread=ufilesize-total_bytes;

    if (Readn(sockfd,buffer,onread) == false)
    {
      printf("Readn() failed.\n"); fclose(fp); fp=NULL; return false;
    }
    
    fwrite(buffer,1,onread,fp); //一次读1个字节读onread次
    total_bytes = total_bytes + onread;
    if ((int)total_bytes == ufilesize) break;
  }
  fclose(fp);
  // 读完后重置文件原始的时间,不是本地接收生成的时间
  UTime(strfilename,strmtime);
  return true;
}

如下传二进制文件。
在这里插入图片描述

7.2 文件下载模块:不建议将tcpgetfile.cpp,tcpfileserver.cpp反过来,虽然全双工但会出现连不上服务器被上网行为审计系统拦截

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

// 这是一个通用的功能模块,采用TCP协议获取文件的 客户端tcpgetfile.cpp
#include "_public.h"
struct st_arg
{
  char ip[31];              // 服务器端的IP地址。
  int  port;                // 服务器端的端口。
  int  ptype;         // 文件获取成功后文件的处理方式:1-保留文件;2-删除文件;3-移动到备份目录。
  char clientpath[301];     // 本地文件存放的根目录。
  char srvpath[301];        // 服务端文件存放的根目录。
  char srvpathbak[301];     // 文件成功获取后,服务端文件备份的根目录,当ptype==3时有效。
  bool andchild;            // 是否获取srvpath目录下各级子目录的文件,true-是;false-否。
  char matchname[301];      // 待获取文件名的匹配方式,如"*.TXT,*.XML",注意用大写。
  char okfilename[301];     // 已获取成功文件名清单。listfilename不需要了,服务端返回的报文直接放容器里了
  int  timetvl;             // 扫描本地目录文件的时间间隔,单位:秒。
} starg;
char strRecvBuffer[TCPBUFLEN+10]; // 接收报文的缓冲区
char strSendBuffer[TCPBUFLEN+10]; // 发送报文的缓冲区
vector<struct st_fileinfo> vlistfile,vlistfile1;
vector<struct st_fileinfo> vokfilename,vokfilename1;
bool LoadListFile(); // 把服务端srvpath目录下的文件加载到vlistfile容器中
bool LoadOKFileName(); // 把okfilename文件内容加载到vokfilename容器中
// 把vlistfile容器中的文件与vokfilename容器中文件对比,得到两个容器
// 一、在vlistfile中存在,并已经采集成功的文件vokfilename1
// 二、在vlistfile中存在,新文件或需要重新采集的文件vlistfile1
bool CompVector();
bool WriteToOKFileName(); // 把vokfilename1容器中的内容先写入okfilename文件中,覆盖之前的旧okfilename文件
bool AppendToOKFileName(struct st_fileinfo *stfileinfo); // 如果ptype==1,把采集成功的文件记录追加到okfilename文件中
CTcpClient TcpClient;
CLogFile logfile;
bool _tcpgetfiles();
void EXIT(int sig);
void _help(char *argv[]);  
bool _xmltoarg(char *strxmlbuffer); // 把xml解析到参数starg结构中
bool ClientLogin(const char *argv); // 登录服务器
bool ActiveTest(); // 向服务端发送心跳报文
bool _tcpgetfiles(); // 实现文件获取的功能

int main(int argc,char *argv[])
{
  if (argc!=3) { _help(argv); return -1; }
  CloseIOAndSignal();
  signal(SIGINT,EXIT); signal(SIGTERM,EXIT);
  if (logfile.Open(argv[1],"a+")==false)
  {
    printf("打开日志文件失败(%s)。\n",argv[1]); return -1;
  }

  if (_xmltoarg(argv[2])==false) return -1;   //把xml解析到参数starg结构中
  while (true)
  {     
    ClientLogin(argv[2]); // 向服务器发起连接并登录
    // 实现文件获取的功能,_tcpgetfiles()出现通讯故障没有关socket,_tcpgetfiles函数返回后vlistfile容器是不空的
    //循环到了ClientLogin这里判断登录,ClientLogin里不判断socket有没有问题不会去重新登录,又到_tcpgetfiles死循环
    _tcpgetfiles();
    if (vlistfile.size()==0)
    {     
      ActiveTest();     // 向服务端发送心跳报文
      sleep(starg.timetvl);
    }
  }
  return 0;
}

void EXIT(int sig)
{
  logfile.Write("程序退出,sig=%d\n\n",sig);
  TcpClient.Close();
  exit(0);
}

//111111111111111111111111111111111111111111111111111显示程序的帮助
void _help(char *argv[])
{
  printf("\n");
  printf("Using:/htidc/public/bin/tcpgetfiles logfilename xmlbuffer\n\n");

  printf("Sample:/htidc/public/bin/tcpgetfiles /log/shqx/tcpgetfiles_surfdata.log \"<ip>172.16.0.15</ip><port>5010</port><ptype>1</ptype><clientpath>/data/shqx/sdata/surfdata</clientpath><srvpath>/data/shqx/tcp/surfdata</srvpath><srvpathbak>/data/shqx/tcp/surfdatabak</srvpathbak><andchild>true</andchild><matchname>SURF_*.TXT,*.DAT</matchname><okfilename>/data/shqx/tcplist/tcpgetfiles_surfdata.xml</okfilename><timetvl>10</timetvl>\"\n\n\n");

  printf("这是一个通用的功能模块,采用TCP协议获取文件的客户端。\n");
  printf("logfilename   本程序运行的日志文件。\n");
  printf("xmlbuffer     本程序运行的参数,如下:\n");
  printf("ip            服务器端的IP地址。\n");
  printf("port          服务器端的端口。\n");
  printf("clientpath    客户端文件存放的根目录。\n");
  printf("srvpath       服务端文件存放的根目录。\n");
  printf("ptype         文件获取成功后服务端文件的处理方式:1-保留文件;2-删除文件;3-移动到备份目录。\n");
  printf("srvpathbak    文件成功获取后,服务端文件备份的根目录,当ptype==3时有效,缺省为空。\n");
  printf("andchild      是否获取srvpath目录下各级子目录的文件,true-是;false-否,缺省为false。\n");
  printf("matchname     待获取文件名的匹配方式,如\"*.TXT,*.XML\",注意用大写。\n");
  printf("okfilename    已获取成功文件名清单,缺省为空。\n");
  printf("timetvl       扫描本地目录文件的时间间隔,单位:秒,取值在1-50之间。\n\n\n");
}

//1111111111111111111111111111111111111111111111把xml解析到参数starg结构中
bool _xmltoarg(char *strxmlbuffer)
{
  memset(&starg,0,sizeof(struct st_arg));
  GetXMLBuffer(strxmlbuffer,"ip",starg.ip);
  if (strlen(starg.ip)==0) { logfile.Write("ip is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"port",&starg.port);
  if ( starg.port==0) { logfile.Write("port is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"ptype",&starg.ptype);
  if ((starg.ptype!=1)&&(starg.ptype!=2)&&(starg.ptype!=3) ) { logfile.Write("ptype not in (1,2,3).\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"clientpath",starg.clientpath);
  if (strlen(starg.clientpath)==0) { logfile.Write("clientpath is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"srvpathbak",starg.srvpathbak);
  if ((starg.ptype==3)&&(strlen(starg.srvpathbak)==0)) { logfile.Write("srvpathbak is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"srvpath",starg.srvpath);
  if (strlen(starg.srvpath)==0) { logfile.Write("srvpath is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"andchild",&starg.andchild);

  GetXMLBuffer(strxmlbuffer,"matchname",starg.matchname);
  if (strlen(starg.matchname)==0) { logfile.Write("matchname is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"okfilename",starg.okfilename);
  if ((starg.ptype==1)&&(strlen(starg.okfilename)==0)) { logfile.Write("okfilename is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"timetvl",&starg.timetvl);
  if (starg.timetvl==0) { logfile.Write("timetvl is null.\n"); return false; }

  if (starg.timetvl>50) starg.timetvl=50;
  return true;
}

//1111111111111111111111111111111111111111111111111111111登录服务器
bool ClientLogin(const char *argv)
{
  if (TcpClient.m_sockfd>0) return true;
  int ii=0;
  while (true)
  {
    if (ii++>0) sleep(20);    // 第一次进入循环不休眠
    // 向服务器发起连接
    if (TcpClient.ConnectToServer(starg.ip,starg.port) == false)
    {
      logfile.Write("TcpClient.ConnectToServer(%s,%d) failed.\n",starg.ip,starg.port); continue;
    }

    memset(strRecvBuffer,0,sizeof(strRecvBuffer));
    memset(strSendBuffer,0,sizeof(strSendBuffer));
    strcpy(strSendBuffer,argv); strcat(strSendBuffer,"<clienttype>2</clienttype>");
    // logfile.Write("1 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
    if (TcpClient.Write(strSendBuffer) == false)
    {
      logfile.Write("1 TcpClient.Write() failed.\n"); continue;
    }

    if (TcpClient.Read(strRecvBuffer,20) == false)
    {
      logfile.Write("1 TcpClient.Read() failed.\n"); continue;
    }
    // logfile.Write("1 strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx
    break;
  }
  logfile.Write("login(%s,%d) ok.\n",starg.ip,starg.port);
  return true;
}

//11111111111111111111111111111111111111111111111111向服务端发送心跳报文
bool ActiveTest()
{
  memset(strRecvBuffer,0,sizeof(strRecvBuffer));
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  strcpy(strSendBuffer,"<activetest>ok</activetest>");

  // logfile.Write("2 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
  if (TcpClient.Write(strSendBuffer) == false)
  {
    logfile.Write("2 TcpClient.Write() failed.\n"); TcpClient.Close(); return false;
  }

  if (TcpClient.Read(strRecvBuffer,20) == false)
  {
    logfile.Write("2 TcpClient.Read() failed.\n"); TcpClient.Close(); return false;
  }
  // logfile.Write("2 strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx

  if (strcmp(strRecvBuffer,"ok") != 0) { TcpClient.Close(); return false; }
  return true;
}

//111111111111111111111111111111111111111111111111111实现文件获取的功能
bool _tcpgetfiles()
{
  // 把服务端srvpath目录下的文件加载到vlistfile容器中
  if (LoadListFile()==false)
  {
    logfile.Write("LoadListFile() failed.\n"); TcpClient.Close(); return false;
  }
  if (starg.ptype==1)
  {
    // 加载okfilename文件中的内容到容器vokfilename中
    LoadOKFileName();
    // 把vlistfile容器中的文件与vokfilename容器中文件对比,得到两个容器
    // 一、在vlistfile中存在,并已经采集成功的文件vokfilename1
    // 二、在vlistfile中存在,新文件或需要重新采集的文件vlistfile1
    CompVector();    
    WriteToOKFileName(); // 把vokfilename1容器中的内容先写入okfilename文件中,覆盖之前的旧okfilename文件    
    vlistfile.clear(); vlistfile.swap(vlistfile1); // 把vlistfile1容器中的内容复制到vlistfile容器中
  }
  
  for (int ii=0;ii<vlistfile.size();ii++)  // 从服务端逐个获取新文件或已改动过的文件
  {    
    memset(strSendBuffer,0,sizeof(strSendBuffer)); // 向服务端发送将获取(下载)的文件信息
    sprintf(strSendBuffer,"<filename>%s</filename><filesize>%d</filesize><mtime>%s</mtime>",vlistfile[ii].filename,vlistfile[ii].filesize,vlistfile[ii].mtime);
    // logfile.Write("3 strSendBuffer=%s\n",strSendBuffer);     // xxxxxx  
    if (TcpClient.Write(strSendBuffer) == false)
    {
      logfile.Write("3 TcpClient.Write() failed.\n"); TcpClient.Close(); return false;
    }

    // 文件信息已知道,此报文有些多余,但是为了兼容SendFile和RecvFile函数,对性能不会有影响。
    if (TcpClient.Read(strRecvBuffer) == false)
    {
      logfile.Write("3 TcpClient.Read() failed.\n"); TcpClient.Close(); return false;
    }
    // logfile.Write("3 strRecvBuffer=%s\n",strRecvBuffer);     // xxxxxx  
    
    // 把文件名中的clientpath替换成srvpath,要小心第三个参数
    struct st_fileinfo stfileinfo;
    memset(&stfileinfo,0,sizeof(struct st_fileinfo));
    strcpy(stfileinfo.filename,vlistfile[ii].filename);
    strcpy(stfileinfo.mtime,vlistfile[ii].mtime);
    stfileinfo.filesize=vlistfile[ii].filesize;
    UpdateStr(stfileinfo.filename,starg.srvpath,starg.clientpath);
    logfile.Write("get %s ...",stfileinfo.filename);
    // ptype=1是增量传输,对服务端来说什么都不干,保留oklistfile是客户端的事   
    if (RecvFile(&logfile,TcpClient.m_sockfd,&stfileinfo)== false)  // 接收文件的内容
    {
      logfile.Write("RecvFile() failed.\n"); TcpClient.Close(); return false;
    }
    logfile.WriteEx("ok.\n");
    // 如果ptype==1,把采集成功的文件记录追加到okfilename文件中
    if (starg.ptype==1) AppendToOKFileName(&vlistfile[ii]);
  }
  return true;
}

//11111111111111111111111111111111111111111把服务端srvpath目录下的文件加载到vlistfile容器中
bool LoadListFile()
{
  vlistfile.clear();
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  strcpy(strSendBuffer,"<list>"); //向服务端发<list>,就像向ftp服务端发nlist命令一样
  // logfile.Write("4 strSendBuffer=%s\n",strSendBuffer);     // xxxxxx  
  if (TcpClient.Write(strSendBuffer) == false)
  {
    logfile.Write("4 TcpClient.Write() failed.\n"); return false;
  }

  memset(strRecvBuffer,0,sizeof(strRecvBuffer));
  if (TcpClient.Read(strRecvBuffer,20) == false)
  {
    logfile.Write("4 TcpClient.Read() failed.\n"); return false;
  }
  // logfile.Write("4 strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx
  // Read到的报文就是文件总数
  int totalfile=0; 
  GetXMLBuffer(strRecvBuffer,"totalfile",&totalfile);
  struct st_fileinfo stfileinfo;

  for (int ii=0;ii<totalfile;ii++) //利用循环接收文件清单报文,解析出来放入vlistfile容器里
  {
    memset(&stfileinfo,0,sizeof(struct st_fileinfo));

    memset(strRecvBuffer,0,sizeof(strRecvBuffer));
    if (TcpClient.Read(strRecvBuffer,20) == false)
    {
      logfile.Write("5 TcpClient.Read() failed.\n"); return false;
    }
    // logfile.Write("5 strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx

    GetXMLBuffer(strRecvBuffer,"filename",stfileinfo.filename);
    GetXMLBuffer(strRecvBuffer,"filesize",&stfileinfo.filesize);
    GetXMLBuffer(strRecvBuffer,"mtime",stfileinfo.mtime);    
    vlistfile.push_back(stfileinfo);
    // logfile.Write("vlistfile filename=%s,mtime=%s\n",stfileinfo.filename,stfileinfo.mtime);
  }
  return true;
}

//11111111111111111111111111111111111111111111111把okfilename文件内容加载到vokfilename容器中
bool LoadOKFileName()
{
  vokfilename.clear();
  CFile File;
  // 注意:如果程序是第一次采集,okfilename是不存在的,并不是错误,所以也返回true。
  if (File.Open(starg.okfilename,"r") == false) return true;
  struct st_fileinfo stfileinfo;
  char strbuffer[301];
  while (true)
  {
    memset(&stfileinfo,0,sizeof(struct st_fileinfo));
    if (File.Fgets(strbuffer,300,true)==false) break;
    GetXMLBuffer(strbuffer,"filename",stfileinfo.filename,300);
    GetXMLBuffer(strbuffer,"mtime",stfileinfo.mtime,20);
    vokfilename.push_back(stfileinfo);
    // logfile.Write("vokfilename filename=%s,mtime=%s\n",stfileinfo.filename,stfileinfo.mtime);
  }
  return true;
}

//11111111111111111111111111111把vlistfile容器中的文件与vokfilename容器中文件对比,得到两个容器
// 一、在vlistfile中存在,并已经采集成功的文件vokfilename1
// 二、在vlistfile中存在,新文件或需要重新采集的文件vlistfile1
bool CompVector()
{
  vokfilename1.clear();  vlistfile1.clear();
  for (int ii=0;ii<vlistfile.size();ii++)
  {
    int jj=0;
    for (jj=0;jj<vokfilename.size();jj++)
    {
      if ( (strcmp(vlistfile[ii].filename,vokfilename[jj].filename)==0) &&
           (strcmp(vlistfile[ii].mtime,vokfilename[jj].mtime)==0) )
      {
        vokfilename1.push_back(vlistfile[ii]); break;
      }
    }

    if (jj==vokfilename.size())
    {
      vlistfile1.push_back(vlistfile[ii]);
    }
  }

  /*
  for (int ii=0;ii<vokfilename1.size();ii++)
  {
    logfile.Write("vokfilename1 filename=%s,mtime=%s\n",vokfilename1[ii].filename,vokfilename1[ii].mtime);
  }

  for (int ii=0;ii<vlistfile1.size();ii++)
  {
    logfile.Write("vlistfile1 filename=%s,mtime=%s\n",vlistfile1[ii].filename,vlistfile1[ii].mtime);
  }
  */
  return true;
}

//111111111111111把vokfilename1容器中的内容先写入okfilename文件中,覆盖之前的旧okfilename文件
bool WriteToOKFileName()
{
  CFile File;
  if (File.Open(starg.okfilename,"w",false) == false) // 注意,打开文件不要采用缓冲机制
  {
    logfile.Write("File.Open(%s) failed.\n",starg.okfilename); return false;
  }

  for (int ii=0;ii<vokfilename1.size();ii++)
  {
    File.Fprintf("<filename>%s</filename><mtime>%s</mtime>\n",vokfilename1[ii].filename,vokfilename1[ii].mtime);
  }

  return true;
}

//1111111111111111111111如果ptype==1,把采集成功的文件记录追加到okfilename文件中
bool AppendToOKFileName(struct st_fileinfo *stfileinfo)
{
  CFile File;
  if (File.Open(starg.okfilename,"a",false) == false)
  {
    logfile.Write("File.Open(%s) failed.\n",starg.okfilename); return false;
  }
  File.Fprintf("<filename>%s</filename><mtime>%s</mtime>\n",stfileinfo->filename,stfileinfo->mtime);
  return true;
}
//这是一个通用的功能模块,采用TCP协议实现文件传输的服务端,tcpfileserver.cpp多线程。
#include "_public.h"
struct st_arg
{
  int clienttype;
  char ip[31];              // 服务器端的IP地址。
  int  port;                // 服务器端的端口。
  int  ptype;      // 文件发送成功后文件的处理方式:1-保留文件;2-移动到备份目录;3-删除文件。
  char clientpath[301];     // 本地文件存放的根目录。
  char clientpathbak[301];  // 文件成功发送后,本地文件备份的根目录,当ptype==2时有效。
  char srvpath[301];        // 服务端文件存放的根目录。
  char srvpathbak[301];     // 文件成功接收后,服务端文件备份的根目录,当ptype==2时有效。
  bool andchild;            // 是否发送clientpath目录下各级子目录的文件,true-是;false-否。
  char matchname[301];      // 待发送文件名的匹配方式,如"*.TXT,*.XML",注意用大写。
  char okfilename[301];     // 已发送成功文件名清单。
  int  timetvl;             // 扫描本地目录文件的时间间隔,单位:秒。
};
bool _xmltoarg(char *strxmlbuffer,struct st_arg *starg); //把xml解析到参数starg结构中
CLogFile logfile;
bool ClientLogin(int clientfd,struct st_arg *starg); // 等待登录
bool ListFile(int clientfd,struct st_arg *starg); // 列出srvpath目录下文件的清单,返回给客户端。
void EXIT(int sig); // 程序退出时调用的函数
void *pth_main(void *arg); // 与客户端通信线程的主函数
bool RecvFilesMain(int clientfd,struct st_arg *starg); // 接收文件主函数
bool SendFilesMain(int clientfd,struct st_arg *starg); // 发送文件主函数
vector<int> vclientfd;  // 存放客户端已连接的socket的容器
void AddClient(int clientfd);      // 把客户端新的socket加入vclientfd容器中
void RemoveClient(int clientfd);   // 关闭客户端的socket并从vclientfd容器中删除,

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

    printf("Example:/htidc/public/bin/tcpfileserver1 /log/shqx/tcpfileserver1.log 5010\n\n");
    printf("本程序是一个公共功能模块,采用TCP/IP传输文件的服务端。\n");
    printf("本程序采用的是多线程的服务端,多进程的服务端程序是tcpfileserver.cpp。\n");
    printf("logfilename 日志文件名。\n");
    printf("port 用于传输文件的TCP端口。\n");
    return -1;
  }
  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("fileserver started(%s).\n",argv[2]);
  CTcpServer TcpServer; //定义为局部变量
  if (TcpServer.InitServer(atoi(argv[2])) == false)
  {
    logfile.Write("TcpServer.InitServer(%s) failed.\n",argv[2]); return -1;
  }

  AddClient(TcpServer.m_listenfd);   //保存服务端的listenfd到vclientfd
  while (true)
  {    
    if (TcpServer.Accept() == false)  //等待客户端的连接
    {
      logfile.Write("TcpServer.Accept() failed.\n"); continue;
    }
    pthread_t pthid;  //客户端连上后创建一线程,下面将socket参数传进去,与新连接上来的客户端通信
    // int4字节,long8字节,*指针8字节,TcpServer.m_connfd定义的是整数int
    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());    
    AddClient(TcpServer.m_connfd); //保存每个客户端的socket到vclientfd
  }
  return 0;
}

//11111111111111111111111111111111111111111111111111111111111111 
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);
}

//11111111111111111111111111111111111111111111111111111111等待登录
bool ClientLogin(int clientfd,struct st_arg *starg)
{
  int  ibuflen=0;
  char strRecvBuffer[TCPBUFLEN+10]; // 接收报文的缓冲区
  char strSendBuffer[TCPBUFLEN+10]; // 发送报文的缓冲区

  memset(strRecvBuffer,0,sizeof(strRecvBuffer));
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  //以前用TcpServer.Read,现在改为TcpRead,对于线程里没有TcpServer这个对象了
  //TcpServer.Read里也是调用TcpRead
  if (TcpRead(clientfd,strRecvBuffer,&ibuflen,20) == false)
  {
    logfile.Write("1 TcpRead() failed.\n"); return false;
  }
  // logfile.Write("1 strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx

  GetXMLBuffer(strRecvBuffer,"clienttype",&starg->clienttype);

  if ( (starg->clienttype==1) || (starg->clienttype==2) )
    strcpy(strSendBuffer,"ok");
  else
    strcpy(strSendBuffer,"failed");

  // logfile.Write("1 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
  if (TcpWrite(clientfd,strSendBuffer) == false)
  {
    logfile.Write("1 TcpWrite() failed.\n"); return false;
  }

  logfile.Write("login %s(clienttype=%d).\n",strSendBuffer,starg->clienttype);
  if (strcmp(strSendBuffer,"failed") == 0) return false;
  // 把参数解析出来
  _xmltoarg(strRecvBuffer,starg);
  return true;
}

//11111111111111111111111111111111111111111111111111111接收文件主函数
bool RecvFilesMain(int clientfd,struct st_arg *starg)
{
  int  ibuflen=0;
  char strRecvBuffer[TCPBUFLEN+10]; // 接收报文的缓冲区
  char strSendBuffer[TCPBUFLEN+10]; // 发送报文的缓冲区
  while (true)
  {
    memset(strRecvBuffer,0,sizeof(strRecvBuffer));
    memset(strSendBuffer,0,sizeof(strSendBuffer));
    if (TcpRead(clientfd,strRecvBuffer,&ibuflen,80) == false)
    {
      logfile.Write("TcpRead() failed.\n"); return false;
    }
    // logfile.Write("2 strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx
    // 处理心跳报文
    if (strstr(strRecvBuffer,"activetest")!=0)
    {
      strcpy(strSendBuffer,"ok");
      // logfile.Write("2 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
      if (TcpWrite(clientfd,strSendBuffer) == false)
      {
        logfile.Write("2 TcpWrite() failed.\n"); return false;
      }
      continue;
    }
    
    struct st_fileinfo stfileinfo;
    memset(&stfileinfo,0,sizeof(struct st_fileinfo));

    // 获取待接收的文件的时间和大小
    GetXMLBuffer(strRecvBuffer,"filename",stfileinfo.filename);
    GetXMLBuffer(strRecvBuffer,"filesize",&stfileinfo.filesize);
    GetXMLBuffer(strRecvBuffer,"mtime",stfileinfo.mtime);
    // 把文件名中的clientpath替换成srvpath,要小心第三个参数
    UpdateStr(stfileinfo.filename,starg->clientpath,starg->srvpath,false);
    // 接收文件的内容
    if (RecvFile(&logfile,clientfd,&stfileinfo)== false)
    {
      logfile.Write("RecvFile() failed.\n"); return false;
    }
    logfile.Write("recv %s ok.\n",stfileinfo.filename);
  }
  return true;
}

//11111111111111111111111111111111111111111111111111111111发送文件主函数
bool SendFilesMain(int clientfd,struct st_arg *starg)
{
  int  ibuflen=0;
  char strRecvBuffer[TCPBUFLEN+10]; // 接收报文的缓冲区
  char strSendBuffer[TCPBUFLEN+10]; // 发送报文的缓冲区
  while (true)
  {
    memset(strRecvBuffer,0,sizeof(strRecvBuffer));
    if (TcpRead(clientfd,strRecvBuffer,&ibuflen,80) == false)
    {
      logfile.Write("TcpRead() failed.\n"); return false;
    }
    // logfile.Write("3 strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx

    // 处理心跳报文
    if (strstr(strRecvBuffer,"activetest")!=0)
    {
      memset(strSendBuffer,0,sizeof(strSendBuffer));
      strcpy(strSendBuffer,"ok");
      // logfile.Write("3 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
      if (TcpWrite(clientfd,strSendBuffer) == false)
      {
        logfile.Write("3 TcpWrite() failed.\n"); return false;
      }
      continue;
    }
    // 处理获取文件列表报文
    if (strcmp(strRecvBuffer,"<list>")==0)
    {
      if (ListFile(clientfd,starg)==false)
      {
        logfile.Write("ListFile() failed.\n"); return false;
      }
      continue;
    }
    // 取文件报文
    if (strncmp(strRecvBuffer,"<filename>",10)==0)
    {
      // 获取待接收的文件的时间和大小
      struct st_fileinfo stfileinfo;
      memset(&stfileinfo,0,sizeof(struct st_fileinfo));
      GetXMLBuffer(strRecvBuffer,"filename",stfileinfo.filename);
      GetXMLBuffer(strRecvBuffer,"filesize",&stfileinfo.filesize);
      GetXMLBuffer(strRecvBuffer,"mtime",stfileinfo.mtime);
      // 把文件发送给客户端
      if (SendFile(&logfile,clientfd,&stfileinfo)==false) return false;
      logfile.Write("put %s ...ok.\n",stfileinfo.filename);
      // 删除服务端的文件
      if (starg->ptype==2) REMOVE(stfileinfo.filename);
      // 备份服务端的文件
      if (starg->ptype==3) 
      {
        char strfilenamebak[301];
        memset(strfilenamebak,0,sizeof(strfilenamebak));
        strcpy(strfilenamebak,stfileinfo.filename);
        UpdateStr(strfilenamebak,starg->srvpath,starg->srvpathbak,false);  // 要小心第三个参数
        if (RENAME(stfileinfo.filename,strfilenamebak)==false)
        {
          logfile.Write("RENAME %s to %s failed.\n",stfileinfo.filename,strfilenamebak); return false;
        }
      }
    }
  }
  return true;
}

//11111111111111111111111111111111111111111111111111111把xml解析到参数starg结构中
bool _xmltoarg(char *strxmlbuffer,struct st_arg *starg)
{
  GetXMLBuffer(strxmlbuffer,"ip",starg->ip);
  GetXMLBuffer(strxmlbuffer,"port",&starg->port);
  GetXMLBuffer(strxmlbuffer,"ptype",&starg->ptype);
  GetXMLBuffer(strxmlbuffer,"clientpath",starg->clientpath);
  GetXMLBuffer(strxmlbuffer,"clientpathbak",starg->clientpathbak);
  GetXMLBuffer(strxmlbuffer,"srvpath",starg->srvpath);
  GetXMLBuffer(strxmlbuffer,"srvpathbak",starg->srvpathbak);
  GetXMLBuffer(strxmlbuffer,"andchild",&starg->andchild);
  GetXMLBuffer(strxmlbuffer,"matchname",starg->matchname);
  GetXMLBuffer(strxmlbuffer,"okfilename",starg->okfilename);
  GetXMLBuffer(strxmlbuffer,"timetvl",&starg->timetvl);
  return true;
}

//1111111111111111111111111111111111111111111111列出srvpath目录下文件的清单,返回给客户端。
bool ListFile(int clientfd,struct st_arg *starg)
{
  int  ibuflen=0;
  char strRecvBuffer[TCPBUFLEN+10]; // 接收报文的缓冲区
  char strSendBuffer[TCPBUFLEN+10]; // 发送报文的缓冲区
  CDir Dir;
  // 注意,如果目录下的总文件数超过50000,增量发送文件功能将有问题
  if (Dir.OpenDir(starg->srvpath,starg->matchname,50000,starg->andchild,false)==false)
  {
    logfile.Write("Dir.OpenDir(%s) 失败。\n",starg->srvpath); return false;
  }
  // 先把文件总数返回给客户端
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  sprintf(strSendBuffer,"<totalfile>%d</totalfile>",Dir.m_vFileName.size());
  // logfile.Write("4 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
  if (TcpWrite(clientfd,strSendBuffer) == false)
  {
    logfile.Write("4 TcpWrite() failed.\n"); return false;
  }
  // 把文件信息一条条的返回给客户端
  while (true)
  {
    if (Dir.ReadDir()==false) break;
    memset(strSendBuffer,0,sizeof(strSendBuffer));
    sprintf(strSendBuffer,"<filename>%s</filename><mtime>%s</mtime><filesize>%d</filesize>",Dir.m_FullFileName,Dir.m_ModifyTime,Dir.m_FileSize);
    // logfile.Write("5 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
    if (TcpWrite(clientfd,strSendBuffer) == false)
    {
      logfile.Write("5 TcpWrite() failed.\n"); return false;
    }
  }
  return true;
}

//111111111111111111111111111111111111111111111111111111111与客户端通信线程的主函数
void *pth_main(void *arg)
{
  int clientfd=(long) arg; // arg参数为新客户端的socket
  pthread_detach(pthread_self());
  struct st_arg starg;
  memset(&starg,0,sizeof(struct st_arg));
  // 等待客户端的登录
  if (ClientLogin(clientfd,&starg) == false) {  RemoveClient(clientfd); pthread_exit(0); }
  // 接收文件主函数
  if (starg.clienttype==1) 
  {
    if (RecvFilesMain(clientfd,&starg) == false) { RemoveClient(clientfd); pthread_exit(0); }
  }
  // 发送文件主函数
  if (starg.clienttype==2) 
  {
    if (SendFilesMain(clientfd,&starg) == false) { RemoveClient(clientfd); pthread_exit(0); }
  }
  RemoveClient(clientfd); 
  pthread_exit(0);
}

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

//11111111111111111111111111111111111111111关闭客户端的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.3 高性能网络服务:多线程+数据库连接池(多线程每启一个线程都要连数据库耗资源)

如下开始APP服务端设计,客户端就是手机app软件。第一次客户端将手机编号传给服务端,服务端将站点信息传给客户端。
在这里插入图片描述
短连接:客户端即用户点击按钮一次建立一次socket连接请求,处理完一个就断开。响应慢:建立一次socket连接费时间,服务端fork一个进程也要时间,之后和数据库连接也要时间。

长连接:客户端与服务端socket一直连接着进行数据通信,没有数据通信时用心跳(之前文件传输都用的是长连接),用户关了app,连接才断开。费服务端资源:长连接连上后,数据库连接和进程都已准备好,一直通信完才断开。响应快:用户看到数据越快越好控制在1秒内。如下项目组织(shtqapp是一个独立的项目)。
在这里插入图片描述
如下第一行是上面创建用户sql,pdm文件是数据结构设计。
在这里插入图片描述

// 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;
}

数据库连接池的设计可用一个参数去控制连接池的总大小,比如这连接池里有10个connection连接就需要10把锁。在sqlstatement每次想使用数据库连接时就会从10个已创建好的connection看看哪个没锁就拿1个过来用。

//上海天气APP软件服务端主程序。shtqappserver.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];
};
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); // 插入用户请求日志表
vector<int> vclientfd; // 存放客户端已连接的socket的容器
void AddClient(int clientfd);  // 把客户端新的socket加入vclientfd容器中
void RemoveClient(int clientfd);  // 关闭客户端的socket并从vclientfd容器中删除,

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;
  }
  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());    
    AddClient(TcpServer.m_connfd); // 保存每个客户端的socket到vclientfd
  }
  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);
}

//11111111111111111111111111111111111111111与客户端通信线程的主函数
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);
}

//111111111111111111111111111111111111111111把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;
}

//1111111111111111111111111111111111111111心跳业务
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;
}

//11111111111111111111111111111111111111111111新用户登录
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());
  
  strcpy(strSendBuffer,"ok"); //最后发送一个ok
  if (TcpWrite(clientfd,strSendBuffer) == false)
  {
    logfile.Write("biz10001 TcpWrite() failed.\n"); freeconns(conn); return false;
  }
  freeconns(conn);
  return true;
}

//11111111111111111111111111111111111111111111111插入用户请求日志表
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;
}

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

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

//111111111111111111111111111111111111关闭客户端的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; }
  }
}

//111111111111111111111111111111111初始数据库连接池:连接好数据库,初始化锁
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;
}

//11111111111111111111111111111111111111111获得连接池
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);
  }
}

//1111111111111111111111111111111111111111释放连接池
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; //理论上不会来到这里,除非连接池搞乱了
}

在这里插入图片描述
心跳报文业务不需要连接池,先开启服务端。
在这里插入图片描述

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值