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

news2024/9/29 17:33:26

文章目录

  • 1.函数重载:writetofile(),C++true和false,C0和非0
  • 2.类和对象:vprintf
    • 2.1 构造函数:对成员变量初始化
    • 2.2 析构函数:一个类只有一个,不允许被重载
  • 3.引用:C中&取地址,C++中&引用。引用就像起别名,typedef,宏define。对引用的操作与对变量直接操作一样
  • 4.string类:string str,str=,str.c_str()
  • 5.vector容器:vector与string类一样属于STL
  • 6.类继承和多态:class派生类名:public基类名
  • 7.socket:send/recv
    • 7.1 简单文件传输:CTcpClient,CTcpServer
    • 7.2 文件下载模块:不建议将tcpgetfile.cpp,tcpfileserver.cpp反过来,虽然全双工但会出现连不上服务器被上网行为审计系统拦截
    • 7.3 高性能网络服务:多线程+数据库连接池(多线程每启一个线程都要连数据库耗资源)


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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/711545.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

沁恒CH32V307VCT6最小系统板/开发板开源

沁恒CH32V307VCT6最小系统板&#xff0c;引出了所有IO口&#xff0c;一个Type-C连接到USB2.0全速OTG接口&#xff0c;一个Flash芯片 型号W25Q64 容量64Mbit 连接到SPI2接口&#xff0c;板上还有TL432电压基准1.25V(实测1.246V左右)可通过跳线连接到PC3的AD13&#xff0c;还有3.…

【c++】并行编程:cuda入门

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍cuda入门。 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;下次更新不迷路&#x1f95e…

测试在 4090 上运行 vicuna-33b 进行推理

测试在 4090 上运行 vicuna-33b 进行推理 今天尝试在 4090 上运行 vicuna-33b 进行推理&#xff0c;使用的是 8bit 量化。 运行命令如下&#xff0c; python3 -m fastchat.serve.cli --model-path lmsys/vicuna-33b-v1.3 --load-8bit结论&#xff0c;使用 8bit 量化在 4090 上…

HTTP概述以及Tomcat概述

HTTP 概念&#xff1a;Hyper Text Transfer Protocol&#xff0c;超文本传输协议&#xff0c;规定了浏览器和服务器之间数据传输的规则。特点&#xff1a; 基于TCP协议&#xff1a;面向连接、安全基于请求-响应模型的&#xff1a;一次请求对应一次响应HTTP协议是无状态的协议…

Java面试题3

[TOC]目录 1. spring事务 事务简介 事务在逻辑上是一组操作&#xff0c;要么执行&#xff0c;要不都不执行。主要是针对数据库而言的&#xff0c;比如说 MySQL。 为了保证事务是正确可靠的&#xff0c;在数据库进行写入或者更新操作时&#xff0c;就必须得表现出 ACID 的 4 …

nexus搭建maven私有仓库

Nexus 在企业开发中还是比较常用的私有仓库管理工具&#xff0c;一般把公司内部的Maven jar 包或npm包上传到仓库中&#xff0c;有效的对包文件进行管理。 Nexus 至少需要 2G 以上的内存 安装nexus之前首先安装配置好 JDK 环境 和 Maven环境 1、Linux环境安装openJDK 2、Centos…

机器学习——掌握决策树ID3算法的原理,通过增益熵实现手工推导的过程。

文章目录 决策树介绍优缺点ID3算法原理举例 决策树的构建1、特征选择&#xff08;1&#xff09;香农熵&#xff08;2&#xff09;信息增益 2、决策树的生成3、决策树的修剪 总结&#xff1a;参考文献 决策树 介绍 决策树(decision tree)是一种基本的分类与回归方法。ID3是其中…

模拟实现atoi

函数简介 atoi (表示 ascii to integer)是把字符串转换成整型数的一个函数&#xff0c;应用在计算机程序和办公软件中。int atoi(const char *nptr) 函数会扫描参数 nptr字符串&#xff0c;会跳过前面的空白字符&#xff08;例如空格&#xff0c;tab缩进&#xff09;等。如果 n…

socket 网络编程

socket UDP1、单播收发数据测试2、广播发送 TCP1、收发数据测试2、TCP 服务端 Socket是两个程序之间的双向通信链路。它是一种网络通信协议&#xff0c;用于不同计算机之间的进程间通信或同一计算机内部进程之间的通信。 import sockets socket.socket(AddressFamily, Type)…

初识【Matplotlib】

Matplotlib Matplotlib简单介绍Matploylib的分类学习Matploylib网站推荐Matplotlib用法Matplotlib中文字体显示Matplotlib的三层结构容器层图像层辅助显示层 Matplotlib简单介绍 Matplotlib是一个Python的数据可视化库&#xff0c;它提供了一种简单而有效的方式来创建各种类型…

02-独立按键控制LED状态

程序 #include <REGX52.H>void main() {while(1) {if(P3_1 0) //DOWN{P2_0 0xFE;}if(P3_1 1) //UP{P2_0 0XFF;}};}按键的抖动问题 软件实现解决-按键的抖动问题 通过延时解决 #include <REGX52.H>void Delay(unsigned int xms) {while(xms){unsigned cha…

【C语言】整型在内存中存储 赛场(来试试)

前言 &#x1f388;大家好&#xff0c;我是何小侠&#x1f388; &#x1f343;大家可以叫我小何或者小侠&#x1f343; &#x1f490;希望能通过写博客加深自己对于学习内容的理解&#x1f490; &#x1f338;也能帮助更多人理解和学习&#x1f338; 合抱之木&#xff0c;生于…

简单实用配置VScode的C# Framework环境

1. 设置window path环境csc变量 D:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\Roslyn 或者 C:\Windows\Microsoft.NET\Framework64\v4.0.30319 2. 设置"code-runner.executorMap" "csharp": "cd $dir &…

第五课—大学英语四六级备考—听力专项

Key words 1.implement vt.实施 "Implement" 在中文中的意思是「实施」或「执行」。以下是一些示例用法和搭配&#xff1a; 中文意思&#xff1a;实施、执行 形近字&#xff1a;implicate&#xff08;牵连&#xff09; 1. 用英文造句&#xff1a;The government …

【C++初阶(二)】缺省参数以及函数重载

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C初阶之路⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习排序知识   &#x1f51d;&#x1f51d; 缺省参数&#xff06;函数重载 1. 前言2. 缺…

红黑树内容及代码实现

目录 1.概念 2.性质 3.实现 3.1定义数据类型 3.2设计基本操作 3.2.1着色问题详解 3.2.2 代码基本框架 3.2.3着色问题代码 3.2.4红黑树的销毁 3.3验证基本操作 4.总结 1.概念 红黑树是一种二叉搜索树&#xff0c;但是在其中的每个结点上增加一个存储表示该节点的颜…

【数据结构】栈和队列(队列篇)

上期我们已经学习了数据结构中的栈&#xff0c;这期我们开始学习队列。 目录 1.队列的概念及结构 2.队列的实现 队列结构体定义 常用接口函数 初始化队列 队尾入队列 队头出队列 获取队列头部元素、 获取队列队尾元素 获取队列中有效元素个数 检测队列是否为空 销毁…

chatgpt赋能python:用Python轻松给手机用户发送短信——优秀的工具在手,无限可能!

用Python轻松给手机用户发送短信——优秀的工具在手&#xff0c;无限可能&#xff01; 作为一个有10年Python编程经验的工程师&#xff0c;我想分享一下如何用Python给手机用户发送短信。Python是目前非常流行的编程语言之一&#xff0c;它可以轻松地完成很多任务。而给用户发…

13.定时器中断

1.通用定时器工作过程&#xff1a; 2.时钟选择&#xff1a; 内部时钟(CK_INT);外部时钟模式1&#xff1a;外部输入脚(TIx)&#xff1b;外部时钟模式2&#xff1a;外部触发输入(ETR)&#xff1b;内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器&#xff0c;例如可…

【Kafka面试题1】Kafka消费者是pull(拉)还是push(推)模式,这种模式有什么好处?

Kafka消费者是pull(拉)还是push(推)模式&#xff0c;这种模式有什么好处&#xff1f; 一、概述回答 Kafka中的Producer和consumer采用的是push-and-pull模式&#xff0c;即Producer只管向broker push消息&#xff0c;consumer只管从broker pull消息&#xff0c;两者对消息的生…