【C++1】函数重载,类和对象,引用,string类,vector容器,类继承和多态,/socket,进程信号
文章目录
- 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 高性能网络服务:多线程+数据库连接池(多线程每启一个线程都要连数据库耗资源)
- 8.进程:fork(),ps -ef (同-aux) | more
- 9.信号:signal(, EXIT),jps
- 9.1 捕捉信号:ctrl+c:2
- 9.2 捕捉信号:kill -9:9
- 9.3 捕捉信号:kill:15
- 9.4 程序后台运行两种方法:&:ctrl+c无法中止,用killall book1或kill 进程号。if (fork()>0)return 0 父进程退出
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");elseprintf("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); // xxxxxxif (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); // xxxxxxbreak;}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); // xxxxxxif (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); // xxxxxxif (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中存在,新文件或需要重新采集的文件vlistfile1CompVector(); 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); // xxxxxxGetXMLBuffer(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到vclientfdwhile (true){ if (TcpServer.Accept() == false) //等待客户端的连接{logfile.Write("TcpServer.Accept() failed.\n"); continue;}pthread_t pthid; //客户端连上后创建一线程,下面将socket参数传进去,与新连接上来的客户端通信// int4字节,long8字节,*指针8字节,TcpServer.m_connfd定义的是整数intif (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里也是调用TcpReadif (TcpRead(clientfd,strRecvBuffer,&ibuflen,20) == false){logfile.Write("1 TcpRead() failed.\n"); return false;}// logfile.Write("1 strRecvBuffer=%s\n",strRecvBuffer); // xxxxxxGetXMLBuffer(strRecvBuffer,"clienttype",&starg->clienttype);if ( (starg->clienttype==1) || (starg->clienttype==2) )strcpy(strSendBuffer,"ok");elsestrcpy(strSendBuffer,"failed");// logfile.Write("1 strSendBuffer=%s\n",strSendBuffer); // xxxxxxif (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); // xxxxxxif (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); // xxxxxxif (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); // xxxxxxif (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); // xxxxxxif (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参数为新客户端的socketpthread_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]; // 设备IDint 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到vclientfdAddClient(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容器中全部的socketfor (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);// 用户设备IDGetXMLBuffer(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_USERINFOsqlstatement 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"); //最后发送一个okif (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; //理论上不会来到这里,除非连接池搞乱了
}
心跳报文业务不需要连接池,先开启服务端。
8.进程:fork(),ps -ef (同-aux) | more
一个进程(正在内存中运行的程序)在内存里有三部分数据:代码段(相同),堆栈段+数据段(不同)
。getpid库函数功能是获取进程编号,该函数没有参数,返回值是进程的编号(相同程序在不同时间执行,进程编号不同)。
进程应用:并发:把socket服务端改为多进程,每次accept到一个客户端连接后,生成一个子进程,让子进程负责与这个客户端通讯。父进程继续accept客户端连接
。以下在服务端中,在CTcpServer类中增加两个成员函数。
僵尸进程
:一个进程执行了exit系统调用退出时会向父进程发送SIGCHLD信号,而其父进程并没有为它收尸(调用wait或waitpid来获得它的结束状态,如进程ID、终止状态等等)的进程,ps显示有< default >。总结:仔细处理子进程死后的信息,如果不想处理就需要交给系统处理。
9.信号:signal(, EXIT),jps
进程间通信方式:1.管道
:ls | grep 1。
2.消息队列
:内核创建的一个消息队列,os中多个进程都能操作这个消息队列,可以往里面发送消息,可以接收消息。类似socket。
3.共享内存
:每个进程访问内存时,有一个虚拟内存地址和物理内存地址的一个映射,一般两个进程的虚拟内存地址可以一样,但映射的物理内存地址
一般不一样。共享内存
就是将它们映射的物理内存地址也变一样,这时两个进程能同时访问一块相同的物理内存,于是借助这块物理内存实现通信。
4.套接字
:常见,访问数据库进程和数据库进程本身,这两个进程间通信就是通过3306号端口建立起的tcp套接字。本机访问mysql不走tcp的套接字,而是走linux底层套接字。
5.信号量/灯
:类似一个计数器,控制多个进程对一个共享资源的访问,起到控制数量的锁机制。
6.信号
:一个进程可向另一个进程发送一个信号,进程可处理这个信号。如下列出所有信号,linux中信号大多数是把另一个进程杀死,干脆把这个指令叫kill了,如下64种死法。 如下是键盘中断ctrl+c,是当前shell向tail -f这个进程发送一个信号值为2的2)SIGINT
的信号。
9.1 捕捉信号:ctrl+c:2
如下在死循环之前注册下信号的处理,如下让ctrl+c无效。
点击Build后就会将类编译出来。
如上ctrl+c无法停止,只能用kill,jps查看进程pid。
9.2 捕捉信号:kill -9:9
9.3 捕捉信号:kill:15
将如上KILL换成TERM即SIGTERM,重新build project。win下信号比linux下信号少很多,将当前程序打包成jar包传到linux。用如下命令行打包。
如下用压缩软件打开1.jar。
如下:后有一个空格,最后一行是空行。
9.4 程序后台运行两种方法:&:ctrl+c无法中止,用killall book1或kill 进程号。if (fork()>0)return 0 父进程退出
信号作用:服务程序在后台运行,如果想终止它,杀了它不是个好办法,因为没有释放资源。如果能向程序发一个信号,程序收到这个信号后调用一个函数,在函数中编写释放资源代码,程序就可以安全体面退出。
下面 EXIT函数就是自定义函数,TcpServer设为全局变量因为EXIT函数要访问它并关闭socket。
相关文章:
【C++1】函数重载,类和对象,引用,string类,vector容器,类继承和多态,/socket,进程信号
文章目录1.函数重载:writetofile(),Ctrue和false,C0和非02.类和对象:vprintf2.1 构造函数:对成员变量初始化2.2 析构函数:一个类只有一个,不允许被重载3.引用:C中&取地址&#x…...
Spring基础知识
1 简介官网:https://spring.io/projects,Spring发展到今天已经形成了一种开发生态圈,Spring提供了若干个项目,每个项目用于完成特定的功能。Spring Framework是最底层的框架,是其他项目的根基。Spring Boot Spring MVC…...
proxy代理与reflect反射
proxy代理与reflect 在这之前插入一个知识点arguments,每个函数里面都有一个arguments,执行时候不传默认是所有参数,如果传了就是按顺序匹配,箭头函数没有 代理函数 代理对象也就是生成一个替身,然后这个替身处理一切的…...
机器视觉 多模态学习11篇经典论文代码以及解读
此处整理了深度学习-机器视觉,最新的发展方向-多模态学习,中的11篇经典论文,整理了相关解读博客和对应的Github代码,看完此系列论文和博客,相信你能快速切入这个方向。每篇论文、博客或代码都有…...
Redis过期删除策略
目录引出Redis过期删除策略Redis的两种过期策略:定期删除 惰性删除定期删除惰性删除Redis两种过期删除策略存在的问题Redis缓存淘汰策略Redis中的LRU和LFU算法1、LRU(Least Recently Userd最近最少使用)LFU 算法的引入2、LFU(lea…...
数据流分析之def-use链分析
数据流分析之def-use链分析引言1 相关概念2 算法2.1 算法规则2.2 算法流程2.3 算法优化3 举例引言 编译过程中,知道函数中每个指令引用的变量(或虚拟寄存器)来自于前面的哪一次赋值是很有必要的。例如llvm中对store/load转phi优化,就需要准确知道该信息…...
【0175】【内存上下文】如何利用context_freelists[]来彻底释放MemoryContext中分配的所有内存(8 - 2)
文章目录 1. MemoryContext 删除的另一种形式1.1 context_freelists[] 数组1.1.1 context_freelists[0] 和 context_freelists[1] 的意义1.1.2 context_freelists[0] 和 context_freelists[1] 各自功能示意图1.2 context_freelists[] 各成员在删除context时的初始化情况1.2.1 c…...
Redis实战—黑马点评(一) 登录篇
Redis实战 — 黑马点评(一) 登录篇 来自黑马的redis课程的笔记 【黑马程序员Redis入门到实战教程,深度透析redis底层原理redis分布式锁企业解决方案黑马点评实战项目】 目录Redis实战 — 黑马点评(一) 登录篇1. 项目…...
建造者模式-搭建Qt窗口案例
文章目录logging日志输出子线程设计模式可视化插件类界面设计呼吸灯实现综合案例实现本综合案例,应用到如下的知识点。logging日志输出 自定义日志记录器,实现将日志输出到指定的控件中。 # 自定义日志记录器类子线程 threading实现子线程及Qt中的子线…...
*from . import _imaging as core : ImportError: DLL load failed: 找不到指定的模块
错误提示如上。为了解决这个问题,首先参考了解决 from . import _imag…模块。. 首先尝试了彻底卸载pillow:conda uninstall pillow ; pip uninstall pillow 然后重装 pip install pillow,发现问题仍然没有解决。 并且尝试了windo…...
关于尚硅谷Hadoop-报错解决方案日志
以后都会将学习Hadoop中遇到的问题写到这里,供自己参考,能帮到大家更好SecondaryNameNode未启动解决办法:可能是端口被占用(我没遇到)hadoop104未在/etc/hosts配置映射路径我在hadoop104的/etc/hosts 添加了所有hadoop…...
前端高频面试题-HTML和CSS篇(二)
💻 前端高频面试题-HTML和CSS篇(二) 🏠专栏:前端面试题 👀个人主页:繁星学编程🍁 🧑个人简介:一个不断提高自我的平凡人🚀 🔊分享方向…...
神经网络损失函数分布可视化神器
论文名称和地址:Visualizing the Loss Landscape of Neural Netshttps://arxiv.org/pdf/1712.09913.pdf1.1 背景和动机作者主要想研究几个问题:为什么我们能够最小化高度非凸神经损失函数?为什么得到的最小值这个结果具有泛化性?不…...
ansible的部署与命令模块
目录 一、ansible的概述 1、ansible简介 2、ansible特点 3、官方网站 4、ansible的模块组成 5、ansible的工作机制 二、ansible部署 1、ansible的安装 三、ansible的命令行模块 1、command模块 2、shell模块 3、cron模块 4、user模块 5、group模块 6、copy模块 7…...
开发人员与测试人员关系的理解
在软件开发中都会有开发人员(以下简称开发)和测试人员(以下简称测试),在一些小型公司可能并没有测试,仅仅是开发兼任测试。在这里我仅针对于有专业的测试和专业的开发的项目。 每个公司应该都有考核机制&am…...
直面原理:5 张图彻底了解 Android TextToSpeech 机制
ChatGPT 如此火爆,但它的强悍在于 NLU(自然语言理解)、DM(对话管理)和 NLG (自然语言生成)这三块,而 Recognition 识别和 TTS 播报这两块是缺失的。假使你的 App 接入了 ChatGPT&…...
Ruby Socket 编程
Ruby提供了两个级别访问网络的服务,在底层你可以访问操作系统,它可以让你实现客户端和服务器为面向连接和无连接协议的基本套接字支持。 Ruby 统一支持应用程序的网络协议,如FTP、HTTP等。 不管是高层的还是底层的。ruby提供了一些基本类&a…...
Vue3+ElementPlus+koa2实现本地图片的上传
一、示例图二、实现过程利用Koa2书写提交图片的后台接口这个模块是我写的项目中的其中一个板块——上传图片,这个项目的后台接口主要是是使用了后端的Koa2框架,前端小伙伴想要试着自己书写一些增删改查的接口可以从这个入手,Koa2用来了解后端…...
常见漏洞之 Fastjson
数据来源 01 Fastjson相关介绍 》Fastjson概述 》Fastjson历史漏洞 02 Fastson的识别与漏洞发现 》Fastjson寻找 》Fastjson漏洞发现(利用 dnslog) 03 修复建议 建议1:使用fastjson1.2.83版本; Github地址:https:…...
绕过Nginx Host限制
目录绕过Nginx Host限制SNI第三种方法:总结绕过Nginx Host限制 SNI SNI(Server Name Indication)是 TLS 的扩展,这允许在握手过程开始时通过客户端告诉它正在连接的服务器的主机名称。 作用:用来解决一个服务器拥有…...
Visual Studio 2022 常用快捷键,记录一下别忘记~
Visual Studio 2022 常用快捷键,记录一下别忘记~ CtrlEC 注释代码 CtrlEU 取消注释代码 CtrlED 格式化全部代码 CtrlShiftA 新建类 CtrlRG 删除无效Using CtrlH 批量替换 CtrlG 跳转到指定行 CtrlEE 在交互窗口中运行选中代码(很实用) AltEnter 快速引用 …...
软件测试回顾---重点知识
软件测试重点知识回顾 8.1.1软件测试的目的是 尽可能的发现程序中的错误并不是发现所有的错误并不是证明程序是错误的也不是为了调试程序8.1.2白盒测试根据什么设置测试用例?黑盒测试根据什么设置测试用例? 白盒测试根据内部逻辑来设计的黑盒测试根据的是…...
2D图像处理:2D Shape_Base_Matching_缩放_旋转_ICP_显示ROI
文章目录 调试结果参考调试说明问题0:并行运行问题问题1:模板+Mask大小问题问题2:组合缩放和旋转问题3:可以直接将计算边缘的代码删除问题4:如何在原始图像上显示匹配到的ROI问题5:计算的原始旋转角度不需要判断,直接可以在ICP中使用问题6:绘制坐标轴问题7:绘制ROI调试…...
HTTP、HTTPS
目录 1.HTTP 1.1.概述 1.2.报文结构 1.2.1.请求报文 1.2.2.响应报文 1.3.方法 2.HTTPS 1.HTTP 1.1.概述 HTTP,超文本传输协议,WEB体系选用了该协议作为应用层协议。 1.2.报文结构 1.2.1.请求报文 HTTP的请求报文(request࿰…...
计算机网络之http03:HTTPS RSA握手解析
不同的秘钥交换算法,握手过程可能略有差别 上文对HTTPS四次握手的学习 SSL/TLS Secure Sockets Layer/Transport Layer Security 协议握手过程 四次通信:请求服务端公钥 2次 秘钥协商 2次 (1)ClientHello请求 客户端向服务端发送client…...
一款针对EF Core轻量级分表分库、读写分离的开源项目
更多开源项目请查看:一个专注推荐.Net开源项目的榜单 在项目开发中,如果数据量比较大,比如日志记录,我们往往会采用分表分库的方案;为了提升性能,把数据库查询与更新操作分开,这时候就要采用读写…...
Linux环境变量讲解
目录 环境变量 alias命令 type命令 变量分类 Linux最主要的全局环境变量 环境变量 变量是计算机系统用于保存可变数值的数据类型 在Linux中,一般变量都是大写,命令是小写 在Linux中,变量直接使用,不需要定义(更快…...
iptables和nftables的使用
文章目录前言iptable简介iptable命令使用iptables的四表五链nftables简介nftables命令的时候nftables与iptables的区别iptables-legacy和iptables-nft实例将指定protocol:ip:port的流量转发到本地指定端口前言 本文展示了,iptables和nftable命令的使用。 # 实验环…...
中小学信息学相关编程比赛清单及报名网站汇总(C++类)
1、NOI系列比赛(CSP-J CSP-S NOIP NOI APIO CTSC IOI ISIJ等) NOI官网 NOI全国青少年信息学奥林匹克竞赛https://www.noi.cn/ 2、蓝桥杯青少年创意编程大赛 https://www.lanqiaoqingshao.cn/home 3、中国电子协会考评中心...
06Makefile
Makefile 1、Makefile简介 一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂…...
做新疆网站应该做哪方面的/中国新闻网最新消息
目录 1.网络的发展史 1.1网络的由来 1.2网络互联 1.2.1 独立模式(单机模式) 1.2.2局域网(LAN) 1.2.3广域网(WAN) 2.网络通信基础 2.1IP地址 2.2端口号 3.计算机网络协议 3.1网络协议定义 3.2网络协议分层 3.3OSI七层模型 3.4TCP/IP模型(重点&#…...
网监大队让网站备案/网络上市场推广
我们做微信公众号项目的时候,肯定会进行授权,但是这个授权有一个很大很大的bug,就是重定向的时候会默认把#后面的内容给截取掉,然后很容易造成死循环等一系列问题 我们就当需要跳转的链接是这样的: http:22.22.22.22:…...
百度权重从1提升到2的办法/草根seo视频大全网站
记得前面老周写过在.net core 中使用 Composition 的烂文。上回老周给大伙伴们介绍的是一个“重量级”版本—— System.ComponentModel.Composition。应该说,这个“重量级”版本是.NET 框架中的“标配”。 很多东西都会有双面性,MEF 也一样,…...
北京市住房和城乡建设委员会官方网站/站长工具果冻传媒
构造函数的继承 现在有一个"动物"对象的构造函数 function Animal(){this.species "动物";} 还有一个"猫"对象的构造函数 function Cat(name,color){this.name name;this.color color;} 怎样才能使"猫"继承"动物"呢&…...
百度开户/推广优化
1.增加配置 mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启sql日志2.修改日志级别为debug...
怎样开网店流程视频/洛阳seo网络推广
第1章DOM扩展 1.1 获取元素 1、document.getElementsByClassName (class) 通过类名获取元素,以类数组形式存在。 2、document.querySelector(selector) 通过CSS选择器获取元素,符合匹配条件的第1个元素。 3、document.querySelectorAll(selector) 通过CS…...