# 面试专用贴 # 网络编程TCPdemo tcpgetfiles tcppullfiles分别为上传和下载模块 demo中使用的框架类函数定义和实现,在EL文件夹中 ## socket通讯类定义 ``` // socket通讯的客户端类 class ctcpclient { private: int m_connfd; // 客户端的socket. string m_ip; // 服务端的ip地址。 int m_port; // 服务端通讯的端口。 public: ctcpclient(): m_connfd(-1),m_port(0) { } // 构造函数。 // 向服务端发起连接请求。 // ip:服务端的ip地址。 // port:服务端通讯的端口。 // 返回值:true-成功;false-失败。 bool connect(const string &ip,const int port); // 接收对端发送过来的数据。 // buffer:存放接收数据缓冲区。 // ibuflen: 打算接收数据的大小。 // itimeout:等待数据的超时时间(秒):-1-不等待;0-无限等待;>0-等待的秒数。 // 返回值:true-成功;false-失败,失败有两种情况:1)等待超时;2)socket连接已不可用。 bool read(string &buffer,const int itimeout=0); // 接收文本数据。 bool read(void *buffer,const int ibuflen,const int itimeout=0); // 接收二进制数据。 // 向对端发送数据。 // buffer:待发送数据缓冲区。 // ibuflen:待发送数据的大小。 // 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。 bool write(const string &buffer); // 发送文本数据。 bool write(const void *buffer,const int ibuflen); // 发送二进制数据。 // 断开与服务端的连接 void close(); ~ctcpclient(); // 析构函数自动关闭socket,释放资源。 }; // socket通讯的服务端类 class ctcpserver { private: int m_socklen; // 结构体struct sockaddr_in的大小。 struct sockaddr_in m_clientaddr; // 客户端的地址信息。 struct sockaddr_in m_servaddr; // 服务端的地址信息。 int m_listenfd; // 服务端用于监听的socket。 int m_connfd; // 客户端连接上来的socket。 public: ctcpserver():m_listenfd(-1),m_connfd(-1) {} // 构造函数。 // 服务端初始化。 // port:指定服务端用于监听的端口。 // 返回值:true-成功;false-失败,一般情况下,只要port设置正确,没有被占用,初始化都会成功。 bool initserver(const unsigned int port,const int backlog=5); // 从已连接队列中获取一个客户端连接,如果已连接队列为空,将阻塞等待。 // 返回值:true-成功的获取了一个客户端连接,false-失败,如果accept失败,可以重新accept。 bool accept(); // 获取客户端的ip地址。 // 返回值:客户端的ip地址,如"192.168.1.100"。 char *getip(); // 接收对端发送过来的数据。 // buffer:存放接收数据的缓冲区。 // ibuflen: 打算接收数据的大小。 // itimeout:等待数据的超时时间(秒):-1-不等待;0-无限等待;>0-等待的秒数。 // 返回值:true-成功;false-失败,失败有两种情况:1)等待超时;2)socket连接已不可用。 bool read(string &buffer,const int itimeout=0); // 接收文本数据。 bool read(void *buffer,const int ibuflen,const int itimeout=0); // 接收二进制数据。 // 向对端发送数据。 // buffer:待发送数据缓冲区。 // ibuflen:待发送数据的大小。 // 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。 bool write(const string &buffer); // 发送文本数据。 bool write(const void *buffer,const int ibuflen); // 发送二进制数据。 // 关闭监听的socket,即m_listenfd,常用于多进程服务程序的子进程代码中。 void closelisten(); // 关闭客户端的socket,即m_connfd,常用于多进程服务程序的父进程代码中。 void closeclient(); ~ctcpserver(); // 析构函数自动关闭socket,释放资源。 }; ``` ## socket通讯类实现 ``` bool ctcpclient::connect(const string &ip,const int port) { // 如果已连接到服务端,则断开,这种处理方法没有特别的原因,不要纠结。 if (m_connfd!=-1) { ::close(m_connfd); m_connfd=-1; } // 忽略SIGPIPE信号,防止程序异常退出。 // 如果send到一个disconnected socket上,内核就会发出SIGPIPE信号。这个信号 // 的缺省处理方法是终止进程,大多数时候这都不是我们期望的。我们重新定义这 // 个信号的处理方法,大多数情况是直接屏蔽它。 signal(SIGPIPE,SIG_IGN); m_ip=ip; m_port=port; struct hostent* h; struct sockaddr_in servaddr; if ( (m_connfd = socket(AF_INET,SOCK_STREAM,0) ) < 0) return false; if ( !(h = gethostbyname(m_ip.c_str())) ) { ::close(m_connfd); m_connfd=-1; return false; } memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(m_port); // 指定服务端的通讯端口 memcpy(&servaddr.sin_addr,h->h_addr,h->h_length); if (::connect(m_connfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0) { ::close(m_connfd); m_connfd=-1; return false; } return true; } void ctcpclient::close() { if (m_connfd >= 0) ::close(m_connfd); m_connfd=-1; m_port=0; } ctcpclient::~ctcpclient() { close(); } bool ctcpserver::initserver(const unsigned int port,const int backlog) { // 如果服务端的socket>0,关掉它,这种处理方法没有特别的原因,不要纠结。 if (m_listenfd > 0) { ::close(m_listenfd); m_listenfd=-1; } if ( (m_listenfd = socket(AF_INET,SOCK_STREAM,0))<=0) return false; // 忽略SIGPIPE信号,防止程序异常退出。 // 如果往已关闭的socket继续写数据,会产生SIGPIPE信号,它的缺省行为是终止程序,所以要忽略它。 signal(SIGPIPE,SIG_IGN); // 打开SO_REUSEADDR选项,当服务端连接处于TIME_WAIT状态时可以再次启动服务器, // 否则bind()可能会不成功,报:Address already in use。 int opt = 1; setsockopt(m_listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); memset(&m_servaddr,0,sizeof(m_servaddr)); m_servaddr.sin_family = AF_INET; m_servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 任意ip地址。 m_servaddr.sin_port = htons(port); if (bind(m_listenfd,(struct sockaddr *)&m_servaddr,sizeof(m_servaddr)) != 0 ) { closelisten(); return false; } if (listen(m_listenfd,backlog) != 0 ) { closelisten(); return false; } return true; } bool ctcpserver::accept() { if (m_listenfd==-1) return false; int m_socklen = sizeof(struct sockaddr_in); if ((m_connfd=::accept(m_listenfd,(struct sockaddr *)&m_clientaddr,(socklen_t*)&m_socklen)) < 0) return false; return true; } char *ctcpserver::getip() { return(inet_ntoa(m_clientaddr.sin_addr)); } bool ctcpserver::read(void *buffer,const int ibuflen,const int itimeout) // 接收二进制数据。 { if (m_connfd==-1) return false; return(tcpread(m_connfd,buffer,ibuflen,itimeout)); } bool ctcpserver::read(string &buffer,const int itimeout) // 接收文本数据。 { if (m_connfd==-1) return false; return(tcpread(m_connfd,buffer,itimeout)); } bool ctcpclient::read(void *buffer,const int ibuflen,const int itimeout) // 接收二进制数据。 { if (m_connfd==-1) return false; return(tcpread(m_connfd,buffer,ibuflen,itimeout)); } bool ctcpclient::read(string &buffer,const int itimeout) // 接收文本数据。 { if (m_connfd==-1) return false; return(tcpread(m_connfd,buffer,itimeout)); } bool ctcpserver::write(const void *buffer,const int ibuflen) // 发送二进制数据。 { if (m_connfd==-1) return false; return(tcpwrite(m_connfd,(char*)buffer,ibuflen)); } bool ctcpserver::write(const string &buffer) { if (m_connfd==-1) return false; return(tcpwrite(m_connfd,buffer)); } bool ctcpclient::write(const void *buffer,const int ibuflen) { if (m_connfd==-1) return false; return(tcpwrite(m_connfd,(char*)buffer,ibuflen)); } bool ctcpclient::write(const string &buffer) { if (m_connfd==-1) return false; return(tcpwrite(m_connfd,buffer)); } void ctcpserver::closelisten() { if (m_listenfd >= 0) { ::close(m_listenfd); m_listenfd=-1; } } void ctcpserver::closeclient() { if (m_connfd >= 0) { ::close(m_connfd); m_connfd=-1; } } ctcpserver::~ctcpserver() { closelisten(); closeclient(); } bool tcpread(const int sockfd,void *buffer,const int ibuflen,const int itimeout) // 接收二进制数据。 { if (sockfd==-1) return false; // 如果itimeout>0,表示需要等待itimeout秒,如果itimeout秒后还没有数据到达,返回false。 if (itimeout>0) { struct pollfd fds; fds.fd=sockfd; fds.events=POLLIN; if ( poll(&fds,1,itimeout*1000) <= 0 ) return false; } // 如果itimeout==-1,表示不等待,立即判断socket的缓冲区中是否有数据,如果没有,返回false。 if (itimeout==-1) { struct pollfd fds; fds.fd=sockfd; fds.events=POLLIN; if ( poll(&fds,1,0) <= 0 ) return false; } // 读取报文内容。 if (readn(sockfd,(char*)buffer,ibuflen) == false) return false; return true; } ``` ## 日志类定义 ``` // 日志文件 class clogfile { ofstream fout; // 日志文件对象。 string m_filename; // 日志文件名,建议采用绝对路径。 ios::openmode m_mode; // 日志文件的打开模式。 bool m_backup; // 是否自动切换日志。 int m_maxsize; // 当日志文件的大小超过本参数时,自动切换日志。 bool m_enbuffer; // 是否启用文件缓冲区。 spinlock_mutex m_splock; // 自旋锁,用于多线程程序中给写日志的操作加锁。 public: // 构造函数,日志文件的大小缺省100M。 clogfile(int maxsize=100):m_maxsize(maxsize){} // 打开日志文件。 // filename:日志文件名,建议采用绝对路径,如果文件名中的目录不存在,就先创建目录。 // openmode:日志文件的打开模式,缺省值是ios::app。 // bbackup:是否自动切换(备份),true-切换,false-不切换,在多进程的服务程序中,如果多个进程共用一个日志文件,bbackup必须为false。 // benbuffer:是否启用文件缓冲机制,true-启用,false-不启用,如果启用缓冲区,那么写进日志文件中的内容不会立即写入文件,缺省是不启用。 // 注意,在多进程的程序中,多个进程往同一日志文件写入大量的日志时,可能会出现小混乱,但是,多线程不会。 // 1)多个进程往同一日志文件写入大量的日志时,可能会出现小混乱,这个问题并不严重,可以容忍; // 2)只有同时写大量日志时才会出现混乱,在实际开发中,这种情况不多见。 // 3)如果业务无法容忍,可以用信号量加锁。 bool open(const string &filename,const ios::openmode mode=ios::app,const bool bbackup=true,const bool benbuffer=false); // 把日志内容以文本的方式格式化输出到日志文件,并且,在日志内容前面写入时间。 template< typename... Args > bool write(const char* fmt, Args... args) { if (fout.is_open()==false) return false; backup(); // 判断是否需要切换日志文件。 m_splock.lock(); // 加锁。 fout << ltime1() << " " << sformat(fmt,args...); // 把当前时间和日志内容写入日志文件。 m_splock.unlock(); // 解锁。 return fout.good(); } // 重载<<运算符,把日志内容以文本的方式输出到日志文件,不会在日志内容前面写时间。 // 注意:内容换行用\n,不能用endl。 template clogfile& operator<<(const T &value) { m_splock.lock(); fout << value; m_splock.unlock(); return *this; } private: // 如果日志文件的大小超过m_maxsize的值,就把当前的日志文件名改为历史日志文件名,再创建新的当前日志文件。 // 备份后的文件会在日志文件名后加上日期时间,如/tmp/log/filetodb.log.20200101123025。 // 注意,在多进程的程序中,日志文件不可切换,多线的程序中,日志文件可以切换。 bool backup(); public: void close() { fout.close(); } ~clogfile() { close(); }; }; ``` ## 日志类实现 ``` bool clogfile::open(const string &filename,const ios::openmode mode,const bool bbackup,const bool benbuffer) { // 如果日志文件是打开的状态,先关闭它。 if (fout.is_open()) fout.close(); m_filename=filename; // 日志文件名。 m_mode=mode; // 打开模式。 m_backup=bbackup; // 是否自动备份。 m_enbuffer=benbuffer; // 是否启用文件缓冲区。 newdir(m_filename,true); // 如果日志文件的目录不存在,创建它。 fout.open(m_filename,m_mode); // 打开日志文件。 if (m_enbuffer==false) fout << unitbuf; // 是否启用文件缓冲区。 return fout.is_open(); } bool clogfile::backup() { // 不备份 if (m_backup == false) return true; if (fout.is_open() == false) return false; // 如果当前日志文件的大小超过m_maxsize,备份日志。 if (fout.tellp() > m_maxsize*1024*1024) { m_splock.lock(); // 加锁。 fout.close(); // 关闭当前日志文件。 // 拼接备份日志文件名。 string bak_filename=m_filename+"."+ltime1("yyyymmddhh24miss"); rename(m_filename.c_str(),bak_filename.c_str()); // 把当前日志文件改名为备份日志文件。 fout.open(m_filename,m_mode); // 重新打开当前日志文件。 if (m_enbuffer==false) fout << unitbuf; // 判断是否启动文件缓冲区。 m_splock.unlock(); // 解锁。 return fout.is_open(); } return true; } bool cifile::open(const string &filename,const ios::openmode mode) { // 如果文件是打开的状态,先关闭它。 if (fin.is_open()) fin.close(); m_filename=filename; fin.open(m_filename,mode); return fin.is_open(); } ``` ## xml函数族定义 ``` // 解析xml格式字符串的函数族。 // xml格式的字符串的内容如下: // /tmp/_public.h2020-01-01 12:20:3518348 // /tmp/_public.cpp2020-01-01 10:10:1550945 // xmlbuffer:待解析的xml格式字符串。 // fieldname:字段的标签名。 // value:传入变量的地址,用于存放字段内容,支持bool、int、insigned int、long、unsigned long、double和char[]。 // 注意:当value参数的数据类型为char []时,必须保证value数组的内存足够,否则可能发生内存溢出的问题,也可以用ilen参数限定获取字段内容的长度,ilen的缺省值为0,表示不限长度。 // 返回值:true-成功;如果fieldname参数指定的标签名不存在,返回失败。 bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,string &value,const int ilen=0); bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,char *value,const int ilen=0); bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,bool &value); bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,int &value); bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,unsigned int &value); bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,long &value); bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,unsigned long &value); bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,double &value); bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,float &value); ``` ## xml函数族实现 ``` bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,string &value,const int ilen) { string start="<"+fieldname+">"; // 数据项开始的标签。 string end=""; // 数据项结束的标签。 int startp=xmlbuffer.find(start); // 在xml中查找数据项开始的标签的位置。 if (startp==string::npos) return false; int endp=xmlbuffer.find(end); // 在xml中查找数据项结束的标签的位置。 if (endp==string::npos) return false; // 从xml中截取数据项的内容。 int itmplen=endp-startp-start.length(); if ( (ilen>0) && (ilen0) memset(value,0,len+1); // 调用者必须保证value的空间足够,否则这里会内存溢出。 string str; getxmlbuffer(xmlbuffer,fieldname,str); if ( (str.length()<=(unsigned int)len) || (len==0) ) { str.copy(value,str.length()); value[str.length()]=0; // string的copy函数不会给C风格字符串的结尾加0。 } else { str.copy(value,len); value[len]=0; } return true; } bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,bool &value) { string str; if (getxmlbuffer(xmlbuffer,fieldname,str)==false) return false; toupper(str); // 转换为大写来判断(也可以转换为小写,效果相同)。 if (str=="TRUE") value=true; else value=false; return true; } bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,int &value) { string str; if (getxmlbuffer(xmlbuffer,fieldname,str)==false) return false; try { value = stoi(picknumber(str,true)); // stoi有异常,需要处理异常。 } catch(const std::exception& e) { return false; } return true; } bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,unsigned int &value) { string str; if (getxmlbuffer(xmlbuffer,fieldname,str)==false) return false; try { value = stoi(picknumber(str)); // stoi有异常,需要处理异常。不提取符号 + - } catch(const std::exception& e) { return false; } return true; } bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,long &value) { string str; if (getxmlbuffer(xmlbuffer,fieldname,str)==false) return false; try { value = stol(picknumber(str,true)); // stol有异常,需要处理异常。 } catch(const std::exception& e) { return false; } return true; } bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,unsigned long &value) { string str; if (getxmlbuffer(xmlbuffer,fieldname,str)==false) return false; try { value = stoul(picknumber(str)); // stoul有异常,需要处理异常。不提取符号 + - } catch(const std::exception& e) { return false; } return true; } bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,double &value) { string str; if (getxmlbuffer(xmlbuffer,fieldname,str)==false) return false; try { value = stod(picknumber(str,true,true)); // stod有异常,需要处理异常。提取符号和小数点。 } catch(const std::exception& e) { return false; } return true; } bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,float &value) { string str; if (getxmlbuffer(xmlbuffer,fieldname,str)==false) return false; try { value = stof(picknumber(str,true,true)); // stof有异常,需要处理异常。提取符号和小数点。 } catch(const std::exception& e) { return false; } return true; } ```