commit a83e13ff2140ce88b114e480a73257373db92aaa Author: eviwbh Date: Thu Sep 12 18:32:49 2024 +0800 for job diff --git a/EL/README.md b/EL/README.md new file mode 100644 index 0000000..be7cadf --- /dev/null +++ b/EL/README.md @@ -0,0 +1,62 @@ +# 面试专用贴 + +面试官你好,该框架非本人开发,仅作学习整理,但是常用,且熟悉函数源码、原理和流程。 + + + +# EL框架 + +EL框架整理了多个大佬的开源框架,可以提升开发效率,源码方便学习参考 + + + +## 使用 + +拉下来make即可 + +注:使用动态链接库时,需要把动态链接库地址添加到在linux环境变量中。 + + + +## 主要类 + +``` +微秒计时器 +class ctimer + +目录文件列表 +class cdir + +写文件的类 +class cofile + +读取文件的类 +class cifile + +自旋锁 +class spinlock_mutex + +日志文件 +class clogfile + +进程心跳 +class cpactive + +信号量 +class csemp + +循环队列 +template +class squeue + +socket通讯的服务端类 +class ctcpserver + +socket通讯的客户端类 +class ctcpclient +``` + + + + + diff --git a/EL/_cmel.h b/EL/_cmel.h new file mode 100644 index 0000000..0f6574a --- /dev/null +++ b/EL/_cmel.h @@ -0,0 +1,58 @@ +/**************************************************************************************** + * _cmel.h,开发框架公用头文件 +*****************************************************************************************/ + +#ifndef _cmel_H +#define _cmel_H 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif \ No newline at end of file diff --git a/EL/_el.cpp b/EL/_el.cpp new file mode 100644 index 0000000..72229d0 --- /dev/null +++ b/EL/_el.cpp @@ -0,0 +1,1828 @@ +/***************************************************************************************** + * _el.cpp,公共函数和类定义文件。 +/*****************************************************************************************/ + +#include "_el.h" + +namespace eviwbh +{ + +char *deletelchr(char* str, const int cc) +{ + if (str == nullptr) return nullptr; // 如果传进来的是空地址,直接返回,防止程序崩溃。 + + char* p = str; // 指向字符串的首地址。 + while (*p == cc) // 遍历字符串,p将指向左边第一个不是cc的字符。 + p++; + + memmove(str, p, strlen(str) - (p - str)+1); // 把结尾标志0也拷过来。 + + return str; +} + +string& deletelchr(string &str, const int cc) +{ + auto pos=str.find_first_not_of(cc); // 从字符串的左边查找第一个不是cc的字符的位置。 + + if (pos!= 0) str.replace(0,pos,""); // 把0-pos之间的字符串替换成空。 + + return str; +} + +char* deleterchr(char *str,const int cc) +{ + if (str == nullptr) return nullptr; // 如果传进来的是空地址,直接返回,防止程序崩溃。 + + char* p = str; // 指向字符串的首地址。 + char* piscc = 0; // 右边全是字符cc的第一个位置。 + + while (*p != 0) // 遍历字符串。 + { + if (*p == cc && piscc == 0) piscc = p; // 记下字符cc的第一个位置。 + if (*p != cc) piscc = 0; // 只要当前字符不是cc,清空piscc。 + p++; + } + + if (piscc != 0) *piscc = 0; // 把piscc位置的字符置为0,表示字符串已结束。 + + return str; +} + +string& deleterchr(string &str,const int cc) +{ + auto pos=str.find_last_not_of(cc); // 从字符串的右边查找第一个不是cc的字符的位置。 + + if (pos!= 0) str.erase(pos+1); // 把pos之后的字符删掉。 + + return str; +} + +char* deletelrchr(char *str,const int cc) +{ + deletelchr(str,cc); + deleterchr(str,cc); + + return str; +} + +string& deletelrchr(string &str,const int cc) +{ + deletelchr(str,cc); + deleterchr(str,cc); + + return str; +} + +char* toupper(char *str) +{ + if (str == nullptr) return nullptr; + + char* p = str; // 指向字符串的首地址。 + while (*p != 0) // 遍历字符串。 + { + if ( (*p >= 'a') && (*p <= 'z') ) *p=*p - 32; + p++; + } + + return str; +} + +string& toupper(string &str) +{ + for (auto &cc:str) + { + if ( (cc >= 'a') && (cc <= 'z') ) cc=cc - 32; + } + + return str; +} + +char* tolower(char *str) +{ + if (str == nullptr) return nullptr; + + char* p = str; // 指向字符串的首地址。 + while (*p != 0) // 遍历字符串。 + { + if ( (*p >= 'A') && (*p <= 'Z') ) *p=*p + 32; + p++; + } + + return str; +} + +string& tolower(string &str) +{ + for (auto &cc:str) + { + if ( (cc >= 'A') && (cc <= 'Z') ) cc=cc + 32; + } + + return str; +} + +bool replacestr(string &str,const string &str1,const string &str2,bool bloop) +{ + // 如果原字符串str或旧的内容str1为空,没有意义,不执行替换。 + if ( (str.length() == 0) || (str1.length() == 0) ) return false; + + // 如果bloop为true并且str2中包函了str1的内容,直接返回,因为会进入死循环,最终导致内存溢出。 + if ( (bloop==true) && (str2.find(str1)!=string::npos) ) return false; + + int pstart=0; // 如果bloop==false,下一次执行替换的开始位置。 + int ppos=0; // 本次需要替换的位置。 + + while (true) + { + if (bloop == true) + ppos=str.find(str1); // 每次从字符串的最左边开始查找子串str1。 + else + ppos=str.find(str1,pstart); // 从上次执行替换的位置后开始查找子串str1。 + + if (ppos == string::npos) break; // 如果没有找到子串str1。 + + str.replace(ppos,str1.length(),str2); // 把str1替换成str2。 + + if (bloop == false) pstart=ppos+str2.length(); // 下一次执行替换的开始位置往右移动。 + } + + return true; +} + +bool replacestr(char *str,const string &str1,const string &str2,bool bloop) +{ + if (str == nullptr) return false; + + string strtemp(str); + + replacestr(strtemp,str1,str2,bloop); + + strtemp.copy(str,strtemp.length()); + str[strtemp.length()]=0; // string的copy函数不会给C风格字符串的结尾加0。 + + return true; +} + +char* picknumber(const string &src,char *dest,const bool bsigned,const bool bdot) +{ + if (dest==nullptr) return nullptr; // 判断空指针。 + + string strtemp=picknumber(src,bsigned,bdot); + strtemp.copy(dest,strtemp.length()); + dest[strtemp.length()]=0; // string的copy函数不会给C风格字符串的结尾加0。 + + return dest; +} + +string& picknumber(const string &src,string &dest,const bool bsigned,const bool bdot) +{ + // 为了支持src和dest是同一变量的情况,定义str临时变量。 + string str; + + for (char cc:src) + { + // 判断是否提取符号。 + if ( (bsigned==true) && ( (cc == '+') || (cc == '-') )) + { + str.append(1,cc); continue; + } + + // 判断是否提取小数点。 + if ( (bdot==true) && (cc == '.') ) + { + str.append(1,cc); continue; + } + + // 提取数字。 + if (isdigit(cc)) str.append(1,cc); + } + + dest=str; + + return dest; +} + +string picknumber(const string &src,const bool bsigned,const bool bdot) +{ + string dest; + picknumber(src,dest,bsigned,bdot); + return dest; +} + +bool matchstr(const string &str,const string &rules) +{ + // 如果匹配规则表达式的内容是空的,返回false。 + if (rules.length() == 0) return false; + + // 如果如果匹配规则表达式的内容是"*",直接返回true。 + if (rules == "*") return true; + + int ii,jj; + int pos1,pos2; + ccmdstr cmdstr,cmdsubstr; + + string filename=str; + string matchstr=rules; + + // 把字符串都转换成大写后再来比较 + toupper(filename); + toupper(matchstr); + + cmdstr.splittocmd(matchstr,","); + + for (ii=0;ii=m_cmdstr.size()) return false; + + // 从xml中截取数据项的内容。 + // 视频中是以下代码: + // value=m_cmdstr[ii]; + // 改为: + int itmplen=m_cmdstr[ii].length(); + if ( (ilen>0) && (ilen=m_cmdstr.size()) || (value==nullptr) ) return false; + + if (len>0) memset(value,0,len+1); // 调用者必须保证value的空间足够,否则这里会内存溢出。 + + if ( (m_cmdstr[ii].length()<=(unsigned int)len) || (len==0) ) + { + m_cmdstr[ii].copy(value,m_cmdstr[ii].length()); + value[m_cmdstr[ii].length()]=0; // string的copy函数不会给C风格字符串的结尾加0。 + } + else + { + m_cmdstr[ii].copy(value,len); + value[len]=0; + } + + return true; +} + +bool ccmdstr::getvalue(const int ii,int &value) const +{ + if (ii>=m_cmdstr.size()) return false; + + try + { + value = stoi(picknumber(m_cmdstr[ii],true)); // stoi有异常,需要处理异常。 + } + catch(const std::exception& e) + { + return false; + } + + return true; +} + +bool ccmdstr::getvalue(const int ii,unsigned int &value) const +{ + if (ii>=m_cmdstr.size()) return false; + + try + { + value = stoi(picknumber(m_cmdstr[ii])); // stoi有异常,需要处理异常。不提取符号 + - + } + catch(const std::exception& e) + { + return false; + } + + return true; +} + +bool ccmdstr::getvalue(const int ii,long &value) const +{ + if (ii>=m_cmdstr.size()) return false; + + try + { + value = stol(picknumber(m_cmdstr[ii],true)); // stol有异常,需要处理异常。 + } + catch(const std::exception& e) + { + return false; + } + + return true; +} + +bool ccmdstr::getvalue(const int ii,unsigned long &value) const +{ + if (ii>=m_cmdstr.size()) return false; + + try + { + value = stoul(picknumber(m_cmdstr[ii])); // stoul有异常,需要处理异常。不提取符号 + - + } + catch(const std::exception& e) + { + return false; + } + + return true; +} + +bool ccmdstr::getvalue(const int ii,double &value) const +{ + if (ii>=m_cmdstr.size()) return false; + + try + { + value = stod(picknumber(m_cmdstr[ii],true,true)); // stod有异常,需要处理异常。提取符号和小数点。 + } + catch(const std::exception& e) + { + return false; + } + + return true; +} + +bool ccmdstr::getvalue(const int ii,float &value) const +{ + if (ii>=m_cmdstr.size()) return false; + + try + { + value = stof(picknumber(m_cmdstr[ii],true,true)); // stof有异常,需要处理异常。提取符号和小数点。 + } + catch(const std::exception& e) + { + return false; + } + + return true; +} + +bool ccmdstr::getvalue(const int ii,bool &value) const +{ + if (ii>=m_cmdstr.size()) return false; + + string str=m_cmdstr[ii]; + toupper(str); // 转换为大写来判断。 + + if (str=="TRUE") value=true; + else value=false; + + return true; +} + +ccmdstr::~ccmdstr() +{ + m_cmdstr.clear(); +} + +ostream& operator<<(ostream& out, const ccmdstr& cmdstr) +{ + for (int ii=0;ii"; // 数据项开始的标签。 + 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中截取数据项的内容。 + // 视频中是以下代码: + // value=xmlbuffer.substr(startp+start.length(),endp-startp-start.length()); + // 改为: + 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; +} + +// 把整数表示的时间转换为字符串表示的时间。 +// ttime:整数表示的时间。 +// strtime:字符串表示的时间。 +// fmt:输出字符串时间strtime的格式,与ttime函数的fmt参数相同,如果fmt的格式不正确,strtime将为空。 +string& timetostr(const time_t ttime,string &strtime,const string &fmt) +{ + //struct tm sttm = *localtime ( &ttime ); // 非线程安全。 + struct tm sttm; localtime_r (&ttime,&sttm); // 线程安全。 + sttm.tm_year=sttm.tm_year+1900; // tm.tm_year成员要加上1900。 + sttm.tm_mon++; // sttm.tm_mon成员是从0开始的,要加1。 + + // 缺省的时间格式。 + if ( (fmt=="") || (fmt=="yyyy-mm-dd hh24:mi:ss") ) + { + strtime=sformat("%04u-%02u-%02u %02u:%02u:%02u",sttm.tm_year,sttm.tm_mon,sttm.tm_mday,\ + sttm.tm_hour,sttm.tm_min,sttm.tm_sec); + return strtime; + } + + if (fmt=="yyyy-mm-dd hh24:mi") + { + strtime=sformat("%04u-%02u-%02u %02u:%02u",sttm.tm_year,sttm.tm_mon,sttm.tm_mday,\ + sttm.tm_hour,sttm.tm_min); + return strtime; + } + + if (fmt=="yyyy-mm-dd hh24") + { + strtime=sformat("%04u-%02u-%02u %02u",sttm.tm_year,sttm.tm_mon,sttm.tm_mday,sttm.tm_hour); + return strtime; + } + + if (fmt=="yyyy-mm-dd") + { + strtime=sformat("%04u-%02u-%02u",sttm.tm_year,sttm.tm_mon,sttm.tm_mday); + return strtime; + } + + if (fmt=="yyyy-mm") + { + strtime=sformat("%04u-%02u",sttm.tm_year,sttm.tm_mon); + return strtime; + } + + if (fmt=="yyyymmddhh24miss") + { + strtime=sformat("%04u%02u%02u%02u%02u%02u",sttm.tm_year,sttm.tm_mon,sttm.tm_mday,\ + sttm.tm_hour,sttm.tm_min,sttm.tm_sec); + return strtime; + } + + if (fmt=="yyyymmddhh24mi") + { + strtime=sformat("%04u%02u%02u%02u%02u",sttm.tm_year,sttm.tm_mon,sttm.tm_mday,\ + sttm.tm_hour,sttm.tm_min); + return strtime; + } + + if (fmt=="yyyymmddhh24") + { + strtime=sformat("%04u%02u%02u%02u",sttm.tm_year,sttm.tm_mon,sttm.tm_mday,sttm.tm_hour); + return strtime; + } + + if (fmt=="yyyymmdd") + { + strtime=sformat("%04u%02u%02u",sttm.tm_year,sttm.tm_mon,sttm.tm_mday); + return strtime; + } + + if (fmt=="hh24miss") + { + strtime=sformat("%02u%02u%02u",sttm.tm_hour,sttm.tm_min,sttm.tm_sec); + return strtime; + } + + if (fmt=="hh24mi") + { + strtime=sformat("%02u%02u",sttm.tm_hour,sttm.tm_min); + return strtime; + } + + if (fmt=="hh24") + { + strtime=sformat("%02u",sttm.tm_hour); + return strtime; + } + + if (fmt=="mi") + { + strtime=sformat("%02u",sttm.tm_min); + return strtime; + } + + return strtime; +} + +char* timetostr(const time_t ttime,char *strtime,const string &fmt) +{ + if (strtime==nullptr) return nullptr; // 判断空指针。 + + string str; + timetostr(ttime,str,fmt); // 直接调用string& timetostr(const time_t ttime,string &strtime,const string &fmt=""); + str.copy(strtime,str.length()); + strtime[str.length()]=0; // string的copy函数不会给C风格字符串的结尾加0。 + + return strtime; +} + +string timetostr1(const time_t ttime,const string &fmt) +{ + string str; + timetostr(ttime,str,fmt); // 直接调用string& timetostr(const time_t ttime,string &strtime,const string &fmt=""); + return str; +} + +string& ltime(string &strtime,const string &fmt,const int timetvl) +{ + time_t timer; + time(&timer ); // 获取系统当前时间。 + + timer=timer+timetvl; // 加上时间的偏移量。 + + timetostr(timer,strtime,fmt); // 把整数表示的时间转换为字符串表示的时间。 + + return strtime; +} + +char* ltime(char *strtime,const string &fmt,const int timetvl) +{ + if (strtime==nullptr) return nullptr; // 判断空指针。 + + time_t timer; + time(&timer ); // 获取系统当前时间。 + + timer=timer+timetvl; // 加上时间的偏移量。 + + timetostr(timer,strtime,fmt); // 把整数表示的时间转换为字符串表示的时间。 + + return strtime; +} + +string ltime1(const string &fmt,const int timetvl) +{ + string strtime; + + ltime(strtime,fmt,timetvl); // 直接调用string& ltime(string &strtime,const string &fmt="",const int timetvl=0); + + return strtime; +} + +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(); +} + +int cifile::read(void *buf,const int bufsize) +{ + // fin.read((char *)buf,bufsize); + fin.read(static_cast(buf),bufsize); + + return fin.gcount(); // 返回读取的字节数。 +} + +bool cifile::closeandremove() +{ + if (fin.is_open()==false) return false; + + fin.close(); + + if (remove(m_filename.c_str())!=0) return false; + + return true; +} + +void cifile::close() +{ + if (fin.is_open()==false) return; + + fin.close(); +} + +bool cifile::readline(string &buf,const string& endbz) +{ + buf.clear(); // 清空buf。 + + string strline; // 存放从文件中读取的一行。 + + while (true) + { + getline(fin,strline); // 从文件中读取一行。 + + if (fin.eof()) break; // 如果文件已读完。 + + buf=buf+strline; // 把读取的内容拼接到buf中。 + + if (endbz=="") + return true; // 如果行没有结尾标志。 + else + { + // 如果行有结尾标志,判断本次是否读到了结尾标志,如果没有,继续读,如果有,返回。 + if (buf.find(endbz,buf.length()-endbz.length()) != string::npos) return true; + } + + buf=buf+"\n"; // getline从文件中读取一行的时候,会删除\n,所以,这里要补上\n,因为这个\n不应该被删除。 + } + + return false; +} + +bool cofile::open(const string &filename,const bool btmp,const ios::openmode mode,const bool benbuffer) +{ + // 如果文件是打开的状态,先关闭它。 + if (fout.is_open()) fout.close(); + + m_filename=filename; + + newdir(m_filename,true); // 如果文件的目录不存在,创建目录。 + + if (btmp==true) + { // 采用临时文件的方案。 + m_filenametmp=m_filename+".tmp"; + fout.open(m_filenametmp,mode); + } + else + { // 不采用临时文件的方案。 + m_filenametmp.clear(); + fout.open(m_filename,mode); + } + + // 不启用文件缓冲区。 + if (benbuffer==false) fout << unitbuf; + + return fout.is_open(); +} + +bool cofile::write(void *buf,int bufsize) +{ + if (fout.is_open()==false) return false; + + // fout.write((char *)buf,bufsize); + fout.write(static_cast(buf),bufsize); + + return fout.good(); +} + +// 关闭文件,并且把临时文件名改为正式文件名。 +bool cofile::closeandrename() +{ + if (fout.is_open()==false) return false; + + fout.close(); + + // 如果采用了临时文件的方案。 + if (m_filenametmp.empty()==false) + if (rename(m_filenametmp.c_str(),m_filename.c_str())!=0) return false; + + return true; +} + +// 关闭文件,删除临时文件。 +void cofile::close() +{ + if (fout.is_open()==false) return; + + fout.close(); + + // 如果采用了临时文件的方案。 + if (m_filenametmp.empty()==false) + remove(m_filenametmp.c_str()); +} + +bool newdir(const string &pathorfilename,bool bisfilename) +{ + // /tmp/aaa/bbb/ccc/ddd /tmp /tmp/aaa /tmp/aaa/bbb /tmp/aaa/bbb/ccc + + // 检查目录是否存在,如果不存在,逐级创建子目录 + int pos=1; // 不要从0开始,0是根目录/。 + + while (true) + { + int pos1=pathorfilename.find('/',pos); + if (pos1==string::npos) break; + + string strpathname=pathorfilename.substr(0,pos1); // 截取目录。 + + pos=pos1+1; // 位置后移。 + if (access(strpathname.c_str(),F_OK) != 0) // 如果目录不存在,创建它。 + { + // 0755是八进制,不要写成755。 + if (mkdir(strpathname.c_str(),0755) != 0) return false; // 如果目录不存在,创建它。 + } + } + + // 如果pathorfilename不是文件,是目录,还需要创建最后一级子目录。 + if (bisfilename==false) + { + if (access(pathorfilename.c_str(),F_OK) != 0) + { + if (mkdir(pathorfilename.c_str(),0755) != 0) return false; + } + } + + return true; +} + +int filesize(const string &filename) +{ + struct stat st_filestat; // 存放文件信息的结构体。 + + // 获取文件信息,存放在结构体中。 + if (stat(filename.c_str(),&st_filestat) < 0) return -1; + + return st_filestat.st_size; // 返回结构体的文件大小成员。 +} + +bool setmtime(const string &filename,const string &mtime) +{ + struct utimbuf stutimbuf; + + stutimbuf.actime=stutimbuf.modtime=strtotime(mtime); + + if (utime(filename.c_str(),&stutimbuf)!=0) return false; + + return true; +} + +time_t strtotime(const string &strtime) +{ + string strtmp,yyyy,mm,dd,hh,mi,ss; + + picknumber(strtime,strtmp,false,false); // 把字符串中的数字全部提取出来。 + // 2021-12-05 08:30:45 + // 2021/12/05 08:30:45 + // 20211205083045 + + if (strtmp.length() != 14) return -1; // 如果时间格式不是yyyymmddhh24miss,说明时间格式不正确。 + + yyyy=strtmp.substr(0,4); + mm=strtmp.substr(4,2); + dd=strtmp.substr(6,2); + hh=strtmp.substr(8,2); + mi=strtmp.substr(10,2); + ss=strtmp.substr(12,2); + + struct tm sttm; + + try + { + sttm.tm_year = stoi(yyyy) - 1900; + sttm.tm_mon = stoi(mm) - 1; + sttm.tm_mday = stoi(dd); + sttm.tm_hour = stoi(hh); + sttm.tm_min = stoi(mi); + sttm.tm_sec = stoi(ss); + sttm.tm_isdst = 0; + } + catch(const std::exception& e) + { + return -1; + } + + return mktime(&sttm); +} + +bool addtime(const string &in_stime,string &out_stime,const int timetvl,const string &fmt) +{ + time_t timer; + + // 把字符串表示的时间转换为整数表示的时间,方便运算。 + if ( (timer=strtotime(in_stime))==-1) { out_stime=""; return false; } + + timer=timer+timetvl; // 时间运算。 + + // 把整数表示的时间转换为字符串表示的时间。 + timetostr(timer,out_stime,fmt); + + return true; +} + +bool addtime(const string &in_stime,char *out_stime,const int timetvl,const string &fmt) +{ + if (out_stime==nullptr) return false; // 判断空指针。 + + time_t timer; + + // 把字符串表示的时间转换为整数表示的时间,方便运算。 + if ( (timer=strtotime(in_stime))==-1) { strcpy(out_stime,""); return false; } + + timer=timer+timetvl; // 时间运算。 + + // 把整数表示的时间转换为字符串表示的时间。 + timetostr(timer,out_stime,fmt); + + return true; +} + +bool filemtime(const string &filename,string &mtime,const string &fmt) +{ + struct stat st_filestat; // 存放文件信息的结构体。 + + // 获取文件信息,存放在结构体中。 + if (stat(filename.c_str(),&st_filestat) < 0) return false; + + // 把整数表示的时间转换成字符串表示的时间。 + timetostr(st_filestat.st_mtime,mtime,fmt); + + return true; +} + +bool filemtime(const string &filename,char *mtime,const string &fmt) +{ + struct stat st_filestat; // 存放文件信息的结构体。 + + // 获取文件信息,存放在结构体中。 + if (stat(filename.c_str(),&st_filestat) < 0) return false; + + // 把整数表示的时间转换成字符串表示的时间。 + timetostr(st_filestat.st_mtime,mtime,fmt); + + return true; +} + +void cdir::setfmt(const string &fmt) +{ + m_fmt=fmt; +} + +bool cdir::opendir(const string &dirname,const string &rules,const int maxfiles,const bool bandchild,bool bsort) +{ + m_filelist.clear(); // 清空文件列表容器。 + m_pos=0; // 从文件列表中已读取文件的位置归0。 + + // 如果目录不存在,创建它。 + if (newdir(dirname,false) == false) return false; + + // 打开目录,获取目录中的文件列表,存放在m_filelist容器中。 + bool ret=_opendir(dirname,rules,maxfiles,bandchild); + + if (bsort==true) // 对文件列表排序。 + { + sort(m_filelist.begin(), m_filelist.end()); + } + + return ret; +} + +// 这是一个递归函数,在opendir()中调用,cdir类的外部不需要调用它。 +bool cdir::_opendir(const string &dirname,const string &rules,const int maxfiles,const bool bandchild) +{ + DIR *dir; // 目录指针。 + + // 打开目录。 + if ( (dir=::opendir(dirname.c_str())) == nullptr ) return false; // opendir与库函数重名,需要加:: + + string strffilename; // 全路径的文件名。 + struct dirent *stdir; // 存放从目录中读取的内容。 + + // 用循环读取目录的内容,将得到目录中的文件名和子目录。 + while ((stdir=::readdir(dir)) != 0) // readdir与库函数重名,需要加:: + { + // 判断容器中的文件数量是否超出maxfiles参数。 + if ( m_filelist.size()>=maxfiles ) break; + + // 文件名以"."打头的文件不处理。.是当前目录,..是上一级目录,其它以.打头的都是特殊目录和文件。 + if (stdir->d_name[0]=='.') continue; + + // 拼接全路径的文件名。 + strffilename=dirname+'/'+stdir->d_name; + + // 如果是目录,处理各级子目录。 + if (stdir->d_type==4) + { + if (bandchild == true) // 打开各级子目录。 + { + if (_opendir(strffilename,rules,maxfiles,bandchild) == false) // 递归调用_opendir函数。 + { + closedir(dir); return false; + } + } + } + + // 如果是普通文件,放入容器中。 + if (stdir->d_type==8) + { + // 把能匹配上的文件放入m_filelist容器中。 + if (matchstr(stdir->d_name,rules) == false) continue; + + m_filelist.push_back(std::move(strffilename)); + } + } + + closedir(dir); // 关闭目录。 + + return true; +} + +bool cdir::readdir() +{ + // 如果已读完,清空容器 + if (m_pos >= m_filelist.size()) + { + m_pos=0; m_filelist.clear(); return false; + } + + // 文件全名,包括路径 + m_ffilename=m_filelist[m_pos]; + + // 从绝对路径的文件名中解析出目录名和文件名。 + int pp=m_ffilename.find_last_of("/"); + m_dirname=m_ffilename.substr(0,pp); + m_filename=m_ffilename.substr(pp+1); + + // 获取文件的信息。 + struct stat st_filestat; + stat(m_ffilename.c_str(),&st_filestat); + m_filesize=st_filestat.st_size; // 文件大小。 + m_mtime=timetostr1(st_filestat.st_mtime,m_fmt); // 文件最后一次被修改的时间。 + m_ctime=timetostr1(st_filestat.st_ctime,m_fmt); // 文件生成的时间。 + m_atime=timetostr1(st_filestat.st_atime,m_fmt); // 文件最后一次被访问的时间。 + + m_pos++; // 已读取文件的位置后移。 + + return true; +} + +cdir::~cdir() +{ + m_filelist.clear(); +} + +bool renamefile(const string &srcfilename,const string &dstfilename) +{ + // 如果原文件不存在,直接返回失败。 + if (access(srcfilename.c_str(),R_OK) != 0) return false; + + // 创建目标文件的目录。 + if (newdir(dstfilename,true) == false) return false; + + // 调用操作系统的库函数rename重命名文件。 mv + if (rename(srcfilename.c_str(),dstfilename.c_str()) == 0) return true; + + return false; +} + +// 忽略关闭全部的信号、关闭全部的IO,缺省只忽略信号,不关IO。 +// 不希望后台服务程序被信号打扰,需要什么信号可以在程序中设置。 +// 实际上关闭的IO是0、1、2。 +void closeioandsignal(bool bcloseio) +{ + int ii=0; + + for (ii=0;ii<64;ii++) + { + if (bcloseio==true) close(ii); + + signal(ii,SIG_IGN); + } +} + +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; +} + +bool tcpread(const int sockfd,string &buffer,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; + } + + int buflen=0; + + // 先读取报文长度,4个字节。 + if (readn(sockfd,(char*)&buflen,4) == false) return false; + + buffer.resize(buflen); // 设置buffer的大小。 + + // 再读取报文内容。 + if (readn(sockfd,&buffer[0],buflen) == false) return false; + + return true; +} + +bool tcpwrite(const int sockfd,const void *buffer,const int ibuflen) // 发送二进制数据。 +{ + if (sockfd==-1) return false; + + if (writen(sockfd,(char*)buffer,ibuflen) == false) return false; + + return true; +} + +bool tcpwrite(const int sockfd,const string &buffer) // 发送文本数据。 +{ + if (sockfd==-1) return false; + + int buflen=buffer.size(); + + // 先发送报头。 + if (writen(sockfd,(char*)&buflen,4) == false) return false; + + // 再发送报文体。 + if (writen(sockfd,buffer.c_str(),buflen) == false) return false; + + return true; +} + +// 从已经准备好的socket中读取数据。 +// sockfd:已经准备好的socket连接。 +// buffer:接收数据缓冲区的地址。 +// n:本次接收数据的字节数。 +// 返回值:成功接收到n字节的数据后返回true,socket连接不可用返回false。 +bool readn(const int sockfd,char *buffer,const size_t n) +{ + int nleft=n; // 剩余需要读取的字节数。 + int idx=0; // 已成功读取的字节数。 + int nread; // 每次调用recv()函数读到的字节数。 + + while(nleft > 0) + { + if ( (nread=recv(sockfd,buffer+idx,nleft,0)) <= 0) return false; + + idx=idx+nread; + nleft=nleft-nread; + } + + return true; +} + +// 向已经准备好的socket中写入数据。 +// sockfd:已经准备好的socket连接。 +// buffer:待发送数据缓冲区的地址。 +// n:待发送数据的字节数。 +// 返回值:成功发送完n字节的数据后返回true,socket连接不可用返回false。 +bool writen(const int sockfd,const char *buffer,const size_t n) +{ + int nleft=n; // 剩余需要写入的字节数。 + int idx=0; // 已成功写入的字节数。 + int nwritten; // 每次调用send()函数写入的字节数。 + + while(nleft > 0 ) + { + if ( (nwritten=send(sockfd,buffer+idx,nleft,0)) <= 0) return false; + + nleft=nleft-nwritten; + idx=idx+nwritten; + } + + return true; +} + +bool copyfile(const string &srcfilename,const string &dstfilename) +{ + // 创建目标文件的目录。 + if (newdir(dstfilename,true) == false) return false; + + cifile ifile; + cofile ofile; + int ifilesize=filesize(srcfilename); + + int total_bytes=0; + int onread=0; + char buffer[5000]; + + if (ifile.open(srcfilename,ios::in|ios::binary)==false) return false; + + if (ofile.open(dstfilename,ios::out|ios::binary)==false) return false; + + while (true) + { + if ((ifilesize-total_bytes) > 5000) onread=5000; + else onread=ifilesize-total_bytes; + + memset(buffer,0,sizeof(buffer)); + ifile.read(buffer,onread); + ofile.write(buffer,onread); + + total_bytes = total_bytes + onread; + + if (total_bytes == ifilesize) break; + } + + ifile.close(); + ofile.closeandrename(); + + // 更改文件的修改时间属性 + string strmtime; + filemtime(srcfilename,strmtime); + setmtime(dstfilename,strmtime); + + return true; +} + +ctimer::ctimer() +{ + start(); // 计时开始。 +} + +// 计时开始。 +void ctimer::start() +{ + memset(&m_start,0,sizeof(struct timeval)); + memset(&m_end,0,sizeof(struct timeval)); + + gettimeofday(&m_start, 0); // 获取当前时间,精确到微秒。 +} + +// 计算已逝去的时间,单位:秒,小数点后面是微秒 +// 每调用一次本方法之后,自动调用Start方法重新开始计时。 +double ctimer::elapsed() +{ + gettimeofday(&m_end,0); // 获取当前时间作为计时结束的时间,精确到微秒。 + + string str; + str=sformat("%ld.%06ld",m_start.tv_sec,m_start.tv_usec); + double dstart=stod(str); // 把计时开始的时间点转换为double。 + + str=sformat("%ld.%06ld",m_end.tv_sec,m_end.tv_usec); + double dend=stod(str); // 把计时结束的时间点转换为double。 + + start(); // 重新开始计时。 + + return dend-dstart; +} + + cpactive::cpactive() + { + m_shmid=0; + m_pos=-1; + m_shm=0; + } + + // 把当前进程的信息加入共享内存进程组中。 + bool cpactive::addpinfo(const int timeout,const string &pname,clogfile *logfile) + { + if (m_pos!=-1) return true; + + // 创建/获取共享内存,键值为SHMKEYP,大小为MAXNUMP个st_procinfo结构体的大小。 + if ( (m_shmid = shmget((key_t)SHMKEYP, MAXNUMP*sizeof(struct st_procinfo), 0666|IPC_CREAT)) == -1) + { + if (logfile!=nullptr) logfile->write("创建/获取共享内存(%x)失败。\n",SHMKEYP); + else printf("创建/获取共享内存(%x)失败。\n",SHMKEYP); + + return false; + } + + // 将共享内存连接到当前进程的地址空间。 + m_shm=(struct st_procinfo *)shmat(m_shmid, 0, 0); + + /* + struct st_procinfo stprocinfo; // 当前进程心跳信息的结构体。 + memset(&stprocinfo,0,sizeof(stprocinfo)); + stprocinfo.pid=getpid(); // 当前进程号。 + stprocinfo.timeout=timeout; // 超时时间。 + stprocinfo.atime=time(0); // 当前时间。 + strncpy(stprocinfo.pname,pname.c_str(),50); // 进程名。 + */ + st_procinfo stprocinfo(getpid(),pname.c_str(),timeout,time(0)); // 当前进程心跳信息的结构体。 + + // 进程id是循环使用的,如果曾经有一个进程异常退出,没有清理自己的心跳信息, + // 它的进程信息将残留在共享内存中,不巧的是,如果当前进程重用了它的id, + // 守护进程检查到残留进程的信息时,会向进程id发送退出信号,将误杀当前进程。 + // 所以,如果共享内存中已存在当前进程编号,一定是其它进程残留的信息,当前进程应该重用这个位置。 + for (int ii=0;iipid==stprocinfo.pid ) { m_pos=ii; break; } + } + + csemp semp; // 用于给共享内存加锁的信号量id。 + + if (semp.init(SEMKEYP) == false) // 初始化信号量。 + { + if (logfile!=nullptr) logfile->write("创建/获取信号量(%x)失败。\n",SEMKEYP); + else printf("创建/获取信号量(%x)失败。\n",SEMKEYP); + + return false; + } + + semp.wait(); // 给共享内存上锁。 + + // 如果m_pos==-1,表示共享内存的进程组中不存在当前进程编号,那就找一个空位置。 + if (m_pos==-1) + { + for (int ii=0;iipid==0 ) { m_pos=ii; break; } + } + + // 如果m_pos==-1,表示没找到空位置,说明共享内存的空间已用完。 + if (m_pos==-1) + { + if (logfile!=0) logfile->write("共享内存空间已用完。\n"); + else printf("共享内存空间已用完。\n"); + + semp.post(); // 解锁。 + + return false; + } + + // 把当前进程的心跳信息存入共享内存的进程组中。 + memcpy(m_shm+m_pos,&stprocinfo,sizeof(struct st_procinfo)); + + semp.post(); // 解锁。 + + return true; + } + + // 更新共享内存进程组中当前进程的心跳时间。 + bool cpactive::uptatime() + { + if (m_pos==-1) return false; + + (m_shm+m_pos)->atime=time(0); + + return true; + } + + cpactive::~cpactive() + { + // 把当前进程从共享内存的进程组中移去。 + if (m_pos!=-1) memset(m_shm+m_pos,0,sizeof(struct st_procinfo)); + + // 把共享内存从当前进程中分离。 + if (m_shm!=0) shmdt(m_shm); + } + +// 如果信号量已存在,获取信号量;如果信号量不存在,则创建它并初始化为value。 +// 如果用于互斥锁,value填1,sem_flg填SEM_UNDO。 +// 如果用于生产消费者模型,value填0,sem_flg填0。 +bool csemp::init(key_t key,unsigned short value,short sem_flg) +{ + if (m_semid!=-1) return false; // 如果已经初始化了,不必再次初始化。 + + m_sem_flg=sem_flg; + + // 信号量的初始化不能直接用semget(key,1,0666|IPC_CREAT) + // 因为信号量创建后,初始值是0,如果用于互斥锁,需要把它的初始值设置为1, + // 而获取信号量则不需要设置初始值,所以,创建信号量和获取信号量的流程不同。 + + // 信号量的初始化分三个步骤: + // 1)获取信号量,如果成功,函数返回。 + // 2)如果失败,则创建信号量。 + // 3) 设置信号量的初始值。 + + // 获取信号量。 + if ( (m_semid=semget(key,1,0666)) == -1) + { + // 如果信号量不存在,创建它。 + if (errno==ENOENT) + { + // 用IPC_EXCL标志确保只有一个进程创建并初始化信号量,其它进程只能获取。 + if ( (m_semid=semget(key,1,0666|IPC_CREAT|IPC_EXCL)) == -1) + { + if (errno==EEXIST) // 如果错误代码是信号量已存在,则再次获取信号量。 + { + if ( (m_semid=semget(key,1,0666)) == -1) + { + perror("init 1 semget()"); return false; + } + return true; + } + else // 如果是其它错误,返回失败。 + { + perror("init 2 semget()"); return false; + } + } + + // 信号量创建成功后,还需要把它初始化成value。 + union semun sem_union; + sem_union.val = value; // 设置信号量的初始值。 + if (semctl(m_semid,0,SETVAL,sem_union) < 0) + { + perror("init semctl()"); return false; + } + } + else + { perror("init 3 semget()"); return false; } + } + + return true; +} + +// 信号量的P操作(把信号量的值减value),如果信号量的值是0,将阻塞等待,直到信号量的值大于0。 +bool csemp::wait(short value) +{ + if (m_semid==-1) return false; + + struct sembuf sem_b; + sem_b.sem_num = 0; // 信号量编号,0代表第一个信号量。 + sem_b.sem_op = value; // P操作的value必须小于0。 + sem_b.sem_flg = m_sem_flg; + if (semop(m_semid,&sem_b,1) == -1) { perror("p semop()"); return false; } + + return true; +} + +// 信号量的V操作(把信号量的值减value)。 +bool csemp::post(short value) +{ + if (m_semid==-1) return false; + + struct sembuf sem_b; + sem_b.sem_num = 0; // 信号量编号,0代表第一个信号量。 + sem_b.sem_op = value; // V操作的value必须大于0。 + sem_b.sem_flg = m_sem_flg; + if (semop(m_semid,&sem_b,1) == -1) { perror("V semop()"); return false; } + + return true; +} + +// 获取信号量的值,成功返回信号量的值,失败返回-1。 +int csemp::getvalue() +{ + return semctl(m_semid,0,GETVAL); +} + +// 销毁信号量。 +bool csemp::destroy() +{ + if (m_semid==-1) return false; + + if (semctl(m_semid,0,IPC_RMID) == -1) { perror("destroy semctl()"); return false; } + + return true; +} + +csemp::~csemp() +{ +} + +} // namespace diff --git a/EL/_el.h b/EL/_el.h new file mode 100644 index 0000000..a4c84c1 --- /dev/null +++ b/EL/_el.h @@ -0,0 +1,824 @@ +/**************************************************************************************** + * _el.h,公共函数和类声明文件。 +****************************************************************************************/ + +#ifndef __EL_H +#define __EL_H 1 + +#include "_cmel.h" + +using namespace std; + +namespace eviwbh +{ + +////////////////////////////////////////////////////////////////////////// +// 字符串操作 +// 删除字符串左边指定的字符。 +// str:待处理的字符串。 +// cc:需要删除的字符,缺省删除空格。 +char* deletelchr(char* str, const int cc=' '); +string& deletelchr(string &str, const int cc=' '); + +// 删除字符串右边指定的字符。 +// str:待处理的字符串。 +// cc:需要删除的字符,缺省删除空格。 +char* deleterchr(char *str,const int cc=' '); +string& deleterchr(string &str,const int cc=' '); + +// 删除字符串左右两边指定的字符。 +// str:待处理的字符串。 +// chr:需要删除的字符,缺省删除空格。 +char* deletelrchr(char *str,const int cc=' '); +string& deletelrchr(string &str,const int cc=' '); + +// 把字符串中的小写字母转换成大写,忽略不是字母的字符。 +// str:待转换的字符串。 +char* toupper(char *str); +string& toupper(string &str); + +// 把字符串中的大写字母转换成小写,忽略不是字母的字符。 +// str:待转换的字符串。 +char* tolower(char *str); +string& tolower(string &str); + +// 字符串替换函数。 +// 在字符串str中,如果存在字符串str1,就替换为字符串str2。 +// str:待处理的字符串。 +// str1:旧的内容。 +// str2:新的内容。 +// bloop:是否循环执行替换。 +// 注意: +// 1、如果str2比str1要长,替换后str会变长,所以必须保证str有足够的空间,否则内存会溢出(C++风格字符串不存在这个问题)。 +// 2、如果str2中包含了str1的内容,且bloop为true,这种做法存在逻辑错误,replacestr将什么也不做。 +// 3、如果str2为空,表示删除str中str1的内容。 +bool replacestr(char *str ,const string &str1,const string &str2,const bool bloop=false); +bool replacestr(string &str,const string &str1,const string &str2,const bool bloop=false); + +// 从一个字符串中提取出数字、符号和小数点,存放到另一个字符串中。 +// src:原字符串。 +// dest:目标字符串。 +// bsigned:是否提取符号(+和-),true-包括;false-不包括。 +// bdot:是否提取小数点(.),true-包括;false-不包括。 +// 注意:src和dest可以是同一个变量。 +char* picknumber(const string &src,char *dest,const bool bsigned=false,const bool bdot=false); +string& picknumber(const string &src,string &dest,const bool bsigned=false,const bool bdot=false); +string picknumber(const string &src,const bool bsigned=false,const bool bdot=false); + +// 正则表达式,判断一个字符串是否匹配另一个字符串。 +// str:需要判断的字符串,是精确表示的,如文件名"_public.cpp"。 +// rules:匹配规则的表达式,用星号"*"代表任意字符,多个表达式之间用半角的逗号分隔,如"*.h,*.cpp"。 +// 注意:1)str参数不需要支持"*",rules参数支持"*";2)函数在判断str是否匹配rules的时候,会忽略字母的大小写。 +bool matchstr(const string &str,const string &rules); +////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////// +// ccmdstr类用于拆分有分隔符的字符串。 +// 字符串的格式为:字段内容1+分隔符+字段内容2+分隔符+字段内容3+分隔符+...+字段内容n。 +// 例如:"messi,10,striker,30,1.72,68.5,Barcelona",这是足球运动员梅西的资料。 +// 包括:姓名、球衣号码、场上位置、年龄、身高、体重和效力的俱乐部,字段之间用半角的逗号分隔。 +class ccmdstr +{ +private: + vector m_cmdstr; // 存放拆分后的字段内容。 + + ccmdstr(const ccmdstr &) = delete; // 禁用拷贝构造函数。 + ccmdstr &operator=(const ccmdstr &) = delete; // 禁用赋值函数。 +public: + ccmdstr() { } // 构造函数。 + ccmdstr(const string &buffer,const string &sepstr,const bool bdelspace=false); + + const string& operator[](int ii) const // 重载[]运算符,可以像访问数组一样访问m_cmdstr成员。 + { + return m_cmdstr[ii]; + } + + // 把字符串拆分到m_cmdstr容器中。 + // buffer:待拆分的字符串。 + // sepstr:buffer中采用的分隔符,注意,sepstr参数的数据类型不是字符,是字符串,如","、" "、"|"、"~!~"。 + // bdelspace:拆分后是否删除字段内容前后的空格,true-删除;false-不删除,缺省不删除。 + void splittocmd(const string &buffer,const string &sepstr,const bool bdelspace=false); + + // 获取拆分后字段的个数,即m_cmdstr容器的大小。 + int size() const { return m_cmdstr.size(); } + int cmdcount() const { return m_cmdstr.size(); } // 兼容以前的项目。 + + // 从m_cmdstr容器获取字段内容。 + // ii:字段的顺序号,类似数组的下标,从0开始。 + // value:传入变量的地址,用于存放字段内容。 + // 返回值:true-成功;如果ii的取值超出了m_cmdstr容器的大小,返回失败。 + bool getvalue(const int ii,string &value,const int ilen=0) const; // C++风格字符串。视频中没有第三个参数,加上第三个参数更好。 + bool getvalue(const int ii,char *value,const int ilen=0) const; // C风格字符串,ilen缺省值为0-全部长度。 + bool getvalue(const int ii,int &value) const; // int整数。 + bool getvalue(const int ii,unsigned int &value) const; // unsigned int整数。 + bool getvalue(const int ii,long &value) const; // long整数。 + bool getvalue(const int ii,unsigned long &value) const; // unsigned long整数。 + bool getvalue(const int ii,double &value) const; // 双精度double。 + bool getvalue(const int ii,float &value) const; // 单精度float。 + bool getvalue(const int ii,bool &value) const; // bool型。 + + ~ccmdstr(); // 析构函数。 +}; + +// 重载<<运算符,输出ccmdstr::m_cmdstr中的内容,方便调试。 +ostream& operator<<(ostream& out, const ccmdstr& cc); +///////////////////////////////////// ///////////////////////////////////// + +///////////////////////////////////// ///////////////////////////////////// +// 解析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); +///////////////////////////////////// ///////////////////////////////////// + +// C++格式化输出函数模板。 +template< typename... Args > +bool sformat(string &str,const char* fmt, Args... args ) +{ + int len = snprintf( nullptr, 0, fmt, args... ); // 得到格式化输出后字符串的总长度。 + if (len < 0) return false; // 如果调用snprintf失败,返回-1。 + if (len == 0) { str.clear(); return true; } // 如果调用snprintf返回0,表示格式化输出的内容为空。 + + str.resize(len); // 为string分配内存。 + snprintf(&str[0], len + 1, fmt, args... ); // linux平台第二个参数是len+1,windows平台是len。 + return true; +} +template< typename... Args > +string sformat(const char* fmt, Args... args ) +{ + string str; + + int len = snprintf( nullptr, 0, fmt, args... ); // 得到格式化后字符串的长度。 + if (len < 0) return str; // 如果调用snprintf失败,返回-1。 + if (len == 0) return str; // 如果调用snprintf返回0,表示格式化输出的内容为空。; + + str.resize(len); // 为string分配内存。 + snprintf(&str[0], len + 1, fmt, args... ); // linux平台第二个参数是len+1,windows平台是len。 + return str; +} + +///////////////////////////////////// ///////////////////////////////////// +// 时间操作的若干函数。 +/* + 取操作系统的时间(用字符串表示)。 + strtime:用于存放获取到的时间。 + timetvl:时间的偏移量,单位:秒,0是缺省值,表示当前时间,30表示当前时间30秒之后的时间点,-30表示当前时间30秒之前的时间点。 + fmt:输出时间的格式,fmt每部分的含义:yyyy-年份;mm-月份;dd-日期;hh24-小时;mi-分钟;ss-秒, + 缺省是"yyyy-mm-dd hh24:mi:ss",目前支持以下格式: + "yyyy-mm-dd hh24:mi:ss" + "yyyymmddhh24miss" + "yyyy-mm-dd" + "yyyymmdd" + "hh24:mi:ss" + "hh24miss" + "hh24:mi" + "hh24mi" + "hh24" + "mi" + 注意: + 1)小时的表示方法是hh24,不是hh,这么做的目的是为了保持与数据库的时间表示方法一致; + 2)以上列出了常用的时间格式,如果不能满足你应用开发的需求,请修改源代码timetostr()函数增加更多的格式支持; + 3)调用函数的时候,如果fmt与上述格式都匹配,strtime的内容将为空。 + 4)时间的年份是四位,其它的可能是一位和两位,如果不足两位,在前面补0。 +*/ +string& ltime(string &strtime,const string &fmt="",const int timetvl=0); +char * ltime(char *strtime ,const string &fmt="",const int timetvl=0); +// 为了避免重载的岐义,增加ltime1()函数。 +string ltime1(const string &fmt="",const int timetvl=0); + +// 把整数表示的时间转换为字符串表示的时间。 +// ttime:整数表示的时间。 +// strtime:字符串表示的时间。 +// fmt:输出字符串时间strtime的格式,与ltime()函数的fmt参数相同,如果fmt的格式不正确,strtime将为空。 +string& timetostr(const time_t ttime,string &strtime,const string &fmt=""); +char* timetostr(const time_t ttime,char *strtime ,const string &fmt=""); +// 为了避免重载的岐义,增加timetostr1()函数。 +string timetostr1(const time_t ttime,const string &fmt=""); + +// 把字符串表示的时间转换为整数表示的时间。 +// strtime:字符串表示的时间,格式不限,但一定要包括yyyymmddhh24miss,一个都不能少,顺序也不能变。 +// 返回值:整数表示的时间,如果strtime的格式不正确,返回-1。 +time_t strtotime(const string &strtime); + +// 把字符串表示的时间加上一个偏移的秒数后得到一个新的字符串表示的时间。 +// in_stime:输入的字符串格式的时间,格式不限,但一定要包括yyyymmddhh24miss,一个都不能少,顺序也不能变。 +// out_stime:输出的字符串格式的时间。 +// timetvl:需要偏移的秒数,正数往后偏移,负数往前偏移。 +// fmt:输出字符串时间out_stime的格式,与ltime()函数的fmt参数相同。 +// 注意:in_stime和out_stime参数可以是同一个变量的地址,如果调用失败,out_stime的内容会清空。 +// 返回值:true-成功,false-失败,如果返回失败,可以认为是in_stime的格式不正确。 +bool addtime(const string &in_stime,char *out_stime ,const int timetvl,const string &fmt=""); +bool addtime(const string &in_stime,string &out_stime,const int timetvl,const string &fmt=""); +///////////////////////////////////// ///////////////////////////////////// + +///////////////////////////////////// ///////////////////////////////////// +// 这是一个精确到微秒的计时器。 +class ctimer +{ +private: + struct timeval m_start; // 计时开始的时间点。 + struct timeval m_end; // 计时结束的时间点。 +public: + ctimer(); // 构造函数中会调用start方法。 + + void start(); // 开始计时。 + + // 计算已逝去的时间,单位:秒,小数点后面是微秒。 + // 每调用一次本方法之后,自动调用start方法重新开始计时。 + double elapsed(); +}; +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// 根据绝对路径的文件名或目录名逐级的创建目录。 +// pathorfilename:绝对路径的文件名或目录名。 +// bisfilename:指定pathorfilename的类型,true-pathorfilename是文件名,否则是目录名,缺省值为true。 +// 返回值:true-成功,false-失败,如果返回失败,原因有大概有三种情况: +// 1)权限不足;2)pathorfilename参数不是合法的文件名或目录名;3)磁盘空间不足。 +bool newdir(const string &pathorfilename,bool bisfilename=true); + +///////////////////////////////////// ///////////////////////////////////// +// 文件操作相关的函数 + +// 重命名文件,类似Linux系统的mv命令。 +// srcfilename:原文件名,建议采用绝对路径的文件名。 +// dstfilename:目标文件名,建议采用绝对路径的文件名。 +// 返回值:true-成功;false-失败,失败的主要原因是权限不足或磁盘空间不够,如果原文件和目标文件不在同一个磁盘分区,重命名也可能失败。 +// 注意,在重命名文件之前,会自动创建dstfilename参数中包含的目录。 +// 在应用开发中,可以用renamefile()函数代替rename()库函数。 +bool renamefile(const string &srcfilename,const string &dstfilename); + +// 复制文件,类似Linux系统的cp命令。 +// srcfilename:原文件名,建议采用绝对路径的文件名。 +// dstfilename:目标文件名,建议采用绝对路径的文件名。 +// 返回值:true-成功;false-失败,失败的主要原因是权限不足或磁盘空间不够。 +// 注意: +// 1)在复制文件之前,会自动创建dstfilename参数中的目录名。 +// 2)复制文件的过程中,采用临时文件命名的方法,复制完成后再改名为dstfilename,避免中间状态的文件被读取。 +// 3)复制后的文件的时间与原文件相同,这一点与Linux系统cp命令不同。 +bool copyfile(const string &srcfilename,const string &dstfilename); + +// 获取文件的大小。 +// filename:待获取的文件名,建议采用绝对路径的文件名。 +// 返回值:如果文件不存在或没有访问权限,返回-1,成功返回文件的大小,单位是字节。 +int filesize(const string &filename); + +// 获取文件的时间。 +// filename:待获取的文件名,建议采用绝对路径的文件名。 +// mtime:用于存放文件的时间,即stat结构体的st_mtime。 +// fmt:设置时间的输出格式,与ltime()函数相同,但缺省是"yyyymmddhh24miss"。 +// 返回值:如果文件不存在或没有访问权限,返回false,成功返回true。 +bool filemtime(const string &filename,char *mtime ,const string &fmt="yyyymmddhh24miss"); +bool filemtime(const string &filename,string &mtime,const string &fmt="yyyymmddhh24miss"); + +// 重置文件的修改时间属性。 +// filename:待重置的文件名,建议采用绝对路径的文件名。 +// mtime:字符串表示的时间,格式不限,但一定要包括yyyymmddhh24miss,一个都不能少,顺序也不能变。 +// 返回值:true-成功;false-失败,失败的原因保存在errno中。 +bool setmtime(const string &filename,const string &mtime); +/////////////////////////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////// ///////////////////////////////////// +// 获取某目录及其子目录中的文件列表的类。 +class cdir +{ +private: + vector m_filelist; // 存放文件列表的容器(绝对路径的文件名)。 + int m_pos; // 从文件列表m_filelist中已读取文件的位置。 + string m_fmt; // 文件时间格式,缺省"yyyymmddhh24miss"。 + + cdir(const cdir &) = delete; // 禁用拷贝构造函数。 + cdir &operator=(const cdir &) = delete; // 禁用赋值函数。 +public: + // /project/public/_public.h + string m_dirname; // 目录名,例如:/project/public + string m_filename; // 文件名,不包括目录名,例如:_public.h + string m_ffilename; // 绝对路径的文件,例如:/project/public/_public.h + int m_filesize; // 文件的大小,单位:字节。 + string m_mtime; // 文件最后一次被修改的时间,即stat结构体的st_mtime成员。 + string m_ctime; // 文件生成的时间,即stat结构体的st_ctime成员。 + string m_atime; // 文件最后一次被访问的时间,即stat结构体的st_atime成员。 + + cdir():m_pos(0),m_fmt("yyyymmddhh24miss") {} // 构造函数。 + + // 设置文件时间的格式,支持"yyyy-mm-dd hh24:mi:ss"和"yyyymmddhh24miss"两种,缺省是后者。 + void setfmt(const string &fmt); + + // 打开目录,获取目录中文件的列表,存放在m_filelist容器中。 + // dirname,目录名,采用绝对路径,如/tmp/root。 + // rules,文件名的匹配规则,不匹配的文件将被忽略。 + // maxfiles,本次获取文件的最大数量,缺省值为10000个,如果文件太多,可能消耗太多的内存。 + // bandchild,是否打开各级子目录,缺省值为false-不打开子目录。 + // bsort,是否按文件名排序,缺省值为false-不排序。 + // 返回值:true-成功,false-失败。 + bool opendir(const string &dirname,const string &rules,const int maxfiles=10000,const bool bandchild=false,bool bsort=false); + +private: + // 这是一个递归函数,被opendir()的调用,在cdir类的外部不需要调用它。 + bool _opendir(const string &dirname,const string &rules,const int maxfiles,const bool bandchild); + +public: + // 从m_filelist容器中获取一条记录(文件名),同时获取该文件的大小、修改时间等信息。 + // 调用opendir方法时,m_filelist容器被清空,m_pos归零,每调用一次readdir方法m_pos加1。 + // 当m_pos小于m_filelist.size(),返回true,否则返回false。 + bool readdir(); + + unsigned int size() { return m_filelist.size(); } + + ~cdir(); // 析构函数。 +}; +///////////////////////////////////// ///////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// 写文件的类。 +class cofile // class out file +{ +private: + ofstream fout; // 写入文件的对象。 + string m_filename; // 文件名,建议采用绝对路径。 + string m_filenametmp; // 临时文件名,在m_filename后面加".tmp"。 +public: + cofile() {} + bool isopen() const { return fout.is_open(); } // 文件是否已打开。 + + // 打开文件。 + // filename,待打开的文件名。 + // btmp,是否采用临时文件的方案。 + // mode,打开文件的模式。 + // benbuffer,是否启用文件缓冲区。 + bool open(const string &filename,const bool btmp=true,const ios::openmode mode=ios::out,const bool benbuffer=true); + + // 把数据以文本的方式格式化输出到文件。 + template< typename... Args > + bool writeline(const char* fmt, Args... args) + { + if (fout.is_open()==false) return false; + + fout << sformat(fmt,args...); + + return fout.good(); + } + + // 重载<<运算符,把数据以文本的方式输出到文件。 + // 注意:换行只能用\n,不能用endl。 + template + cofile& operator<<(const T &value) + { + fout << value; return *this; + } + + // 把二进制数据写入文件。 + bool write(void *buf,int bufsize); + + // 关闭文件,并且把临时文件名改为正式文件名。 + bool closeandrename(); + + // 关闭文件,如果有临时文件,则删除它。 + void close(); + + ~cofile() { close(); }; +}; +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// 读取文件的类。 +class cifile // class in file +{ +private: + ifstream fin; // 读取文件的对象。 + string m_filename; // 文件名,建议采用绝对路径。 +public: + cifile() {} + + // 判断文件是否已打开。 + bool isopen() const { return fin.is_open(); } + + // 打开文件。 + // filename,待打开的文件名。 + // mode,打开文件的模式。 + bool open(const string &filename,const ios::openmode mode=ios::in); + + // 以行的方式读取文本文件,endbz指定行的结尾标志,缺省为空,没有结尾标志。 + bool readline(string &buf,const string& endbz=""); + + // 读取二进制文件,返回实际读取到的字节数。 + int read(void *buf,const int bufsize); + + // 关闭并删除文件。 + bool closeandremove(); + + // 只关闭文件。 + void close(); + + ~cifile() { close(); } +}; +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// 自旋锁。 +class spinlock_mutex +{ +private: + atomic_flag flag; + + spinlock_mutex(const spinlock_mutex&) = delete; + spinlock_mutex& operator=(const spinlock_mutex) = delete; +public: + spinlock_mutex() + { + flag.clear(); + } + void lock() // 加锁。 + { + while (flag.test_and_set()) + ; + } + void unlock() // 解锁。 + { + flag.clear(); + } +}; + +///////////////////////////////////// ///////////////////////////////////// +// 日志文件。 +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(); }; +}; +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// 以下是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的对端发送过来的数据。 +// sockfd:可用的socket连接。 +// buffer:接收数据缓冲区的地址。 +// ibuflen:本次成功接收数据的字节数。 +// itimeout:读取数据超时的时间,单位:秒,-1-不等待;0-无限等待;>0-等待的秒数。 +// 返回值:true-成功;false-失败,失败有两种情况:1)等待超时;2)socket连接已不可用。 +bool tcpread(const int sockfd,string &buffer,const int itimeout=0); // 读取文本数据。 +bool tcpread(const int sockfd,void *buffer,const int ibuflen,const int itimeout=0); // 读取二进制数据。 + +// 向socket的对端发送数据。 +// sockfd:可用的socket连接。 +// buffer:待发送数据缓冲区的地址。 +// ibuflen:待发送数据的字节数。 +// 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。 +bool tcpwrite(const int sockfd,const string &buffer); // 写入文本数据。 +bool tcpwrite(const int sockfd,const void *buffer,const int ibuflen); // 写入二进制数据。 + +// 从已经准备好的socket中读取数据。 +// sockfd:已经准备好的socket连接。 +// buffer:存放数据的地址。 +// n:本次打算读取数据的字节数。 +// 返回值:成功接收到n字节的数据后返回true,socket连接不可用返回false。 +bool readn(const int sockfd,char *buffer,const size_t n); + +// 向已经准备好的socket中写入数据。 +// sockfd:已经准备好的socket连接。 +// buffer:待写入数据的地址。 +// n:待写入数据的字节数。 +// 返回值:成功写入完n字节的数据后返回true,socket连接不可用返回false。 +bool writen(const int sockfd,const char *buffer,const size_t n); + +// 以上是socket通讯的函数和类 +///////////////////////////////////// ///////////////////////////////////// + +// 忽略关闭全部的信号、关闭全部的IO,缺省只忽略信号,不关IO。 +void closeioandsignal(bool bcloseio=false); + +// 循环队列。 +template +class squeue +{ +private: + bool m_inited; // 队列被初始化标志,true-已初始化;false-未初始化。 + TT m_data[MaxLength]; // 用数组存储循环队列中的元素。 + int m_head; // 队列的头指针。 + int m_tail; // 队列的尾指针,指向队尾元素。 + int m_length; // 队列的实际长度。 + squeue(const squeue &) = delete; // 禁用拷贝构造函数。 + squeue &operator=(const squeue &) = delete; // 禁用赋值函数。 +public: + + squeue() { init(); } // 构造函数。 + + // 循环队列的初始化操作。 + // 注意:如果用于共享内存的队列,不会调用构造函数,必须调用此函数初始化。 + void init() + { + if (m_inited!=true) // 循环队列的初始化只能执行一次。 + { + m_head=0; // 头指针。 + m_tail=MaxLength-1; // 为了方便写代码,初始化时,尾指针指向队列的最后一个位置。 + m_length=0; // 队列的实际长度。 + memset(m_data,0,sizeof(m_data)); // 数组元素清零。 + m_inited=true; + } + } + + // 元素入队,返回值:false-失败;true-成功。 + bool push(const TT &ee) + { + if (full() == true) + { + cout << "循环队列已满,入队失败。\n"; return false; + } + + // 先移动队尾指针,然后再拷贝数据。 + m_tail=(m_tail+1)%MaxLength; // 队尾指针后移。 + m_data[m_tail]=ee; + m_length++; + + return true; + } + + // 求循环队列的长度,返回值:>=0-队列中元素的个数。 + int size() + { + return m_length; + } + + // 判断循环队列是否为空,返回值:true-空,false-非空。 + bool empty() + { + if (m_length == 0) return true; + + return false; + } + + // 判断循环队列是否已满,返回值:true-已满,false-未满。 + bool full() + { + if (m_length == MaxLength) return true; + + return false; + } + + // 查看队头元素的值,元素不出队。 + TT& front() + { + return m_data[m_head]; + } + + // 元素出队,返回值:false-失败;true-成功。 + bool pop() + { + if (empty() == true) return false; + + m_head=(m_head+1)%MaxLength; // 队列头指针后移。 + m_length--; + + return true; + } + + // 显示循环队列中全部的元素。 + // 这是一个临时的用于调试的函数,队列中元素的数据类型支持cout输出才可用。 + void printqueue() + { + for (int ii = 0; ii < size(); ii++) + { + cout << "m_data[" << (m_head+ii)%MaxLength << "],value=" \ + << m_data[(m_head+ii)%MaxLength] << endl; + } + } +}; + +// 信号量。 +class csemp +{ +private: + union semun // 用于信号量操作的共同体。 + { + int val; + struct semid_ds *buf; + unsigned short *arry; + }; + + int m_semid; // 信号量id(描述符)。 + + // 如果把sem_flg设置为SEM_UNDO,操作系统将跟踪进程对信号量的修改情况, + // 在全部修改过信号量的进程(正常或异常)终止后,操作系统将把信号量恢复为初始值。 + // 如果信号量用于互斥锁,设置为SEM_UNDO。 + // 如果信号量用于生产消费者模型,设置为0。 + short m_sem_flg; + + csemp(const csemp &) = delete; // 禁用拷贝构造函数。 + csemp &operator=(const csemp &) = delete; // 禁用赋值函数。 +public: + csemp():m_semid(-1){} + + // 如果信号量已存在,获取信号量;如果信号量不存在,则创建它并初始化为value。 + // 如果用于互斥锁,value填1,sem_flg填SEM_UNDO。 + // 如果用于生产消费者模型,value填0,sem_flg填0。 + bool init(key_t key,unsigned short value=1,short sem_flg=SEM_UNDO); + bool wait(short value=-1); // 信号量的P操作,如果信号量的值是0,将阻塞等待,直到信号量的值大于0。 + bool post(short value=1); // 信号量的V操作。 + int getvalue(); // 获取信号量的值,成功返回信号量的值,失败返回-1。 + bool destroy(); // 销毁信号量。 + ~csemp(); +}; + +// 进程心跳信息的结构体。 +struct st_procinfo +{ + int pid=0; // 进程id。 + char pname[51]={0}; // 进程名称,可以为空。 + int timeout=0; // 超时时间,单位:秒。 + time_t atime=0; // 最后一次心跳的时间,用整数表示。 + st_procinfo() = default; // 有了自定义的构造函数,编译器将不提供默认构造函数,所以启用默认构造函数。 + st_procinfo(const int in_pid,const string & in_pname,const int in_timeout, const time_t in_atime) + :pid(in_pid),timeout(in_timeout),atime(in_atime) { strncpy(pname,in_pname.c_str(),50); } +}; + +// 以下几个宏用于进程的心跳。 +#define MAXNUMP 1000 // 最大的进程数量。 +#define SHMKEYP 0x5095 // 共享内存的key。 +#define SEMKEYP 0x5095 // 信号量的key。 + +// 查看共享内存: ipcs -m +// 删除共享内存: ipcrm -m shmid +// 查看信号量: ipcs -s +// 删除信号量: ipcrm sem semid + +// 进程心跳操作类。 +class cpactive +{ + private: + int m_shmid; // 共享内存的id。 + int m_pos; // 当前进程在共享内存进程组中的位置。 + st_procinfo *m_shm; // 指向共享内存的地址空间。 + + public: + cpactive(); // 初始化成员变量。 + + // 把当前进程的信息加入共享内存进程组中。 + bool addpinfo(const int timeout,const string &pname="",clogfile *logfile=nullptr); + + // 更新共享内存进程组中当前进程的心跳时间。 + bool uptatime(); + + ~cpactive(); // 从共享内存中删除当前进程的心跳记录。 +}; + +} +#endif diff --git a/EL/_ftp.cpp b/EL/_ftp.cpp new file mode 100644 index 0000000..b0d049e --- /dev/null +++ b/EL/_ftp.cpp @@ -0,0 +1,232 @@ +/****************************************************************************************/ +/* 程序名:_ftp.cpp,此程序是开发框架的ftp客户端工具的类的定义文件。 */ +/****************************************************************************************/ + +#include "_ftp.h" + +namespace eviwbh +{ + +cftpclient::cftpclient() +{ + m_ftpconn=0; + + initdata(); + + FtpInit(); + + m_connectfailed=false; + m_loginfailed=false; + m_optionfailed=false; +} + +cftpclient::~cftpclient() +{ + logout(); +} + +void cftpclient::initdata() +{ + m_size=0; + + m_mtime.clear(); +} + +bool cftpclient::login(const string &host,const string &username,const string &password,const int imode) +{ + if (m_ftpconn != 0) { FtpQuit(m_ftpconn); m_ftpconn=0; } + + m_connectfailed=m_loginfailed=m_optionfailed=false; + + if (FtpConnect(host.c_str(),&m_ftpconn) == false) { m_connectfailed=true; return false; } + + if (FtpLogin(username.c_str(),password.c_str(),m_ftpconn) == false) { m_loginfailed=true; return false; } + + if (FtpOptions(FTPLIB_CONNMODE,(long)imode,m_ftpconn) == false) { m_optionfailed=true; return false; } + + return true; +} + +bool cftpclient::logout() +{ + if (m_ftpconn == 0) return false; + + FtpQuit(m_ftpconn); + + m_ftpconn=0; + + return true; +} + +bool cftpclient::get(const string &remotefilename,const string &localfilename,const bool bcheckmtime) +{ + if (m_ftpconn == 0) return false; + + // 创建本地文件目录。 + newdir(localfilename); + + // 生成本地文件的临时文件名。 + string strlocalfilenametmp=localfilename+".tmp"; + + // 获取远程服务器的文件的时间。 + if (mtime(remotefilename) == false) return false; + + // 取文件。 + if (FtpGet(strlocalfilenametmp.c_str(),remotefilename.c_str(),FTPLIB_IMAGE,m_ftpconn) == false) return false; + + // 判断文件下载前和下载后的时间,如果时间不同,表示在文件传输的过程中已发生了变化,返回失败。 + if (bcheckmtime==true) + { + string strmtime=m_mtime; + + if (mtime(remotefilename) == false) return false; + + if (m_mtime!=strmtime) return false; + } + + // 重置文件时间。 + setmtime(strlocalfilenametmp,m_mtime); + + // 改为正式的文件。 + if (rename(strlocalfilenametmp.c_str(),localfilename.c_str()) != 0) return false; + + // 获取文件的大小。 + m_size=filesize(localfilename); + + return true; +} + +bool cftpclient::mtime(const string &remotefilename) +{ + if (m_ftpconn == 0) return false; + + m_mtime.clear(); + + string strmtime; + strmtime.resize(14); + + if (FtpModDate(remotefilename.c_str(),&strmtime[0],14,m_ftpconn) == false) return false; + + // 把UTC时间转换为本地时间。 + addtime(strmtime,m_mtime,0+8*60*60,"yyyymmddhh24miss"); + + return true; +} + +bool cftpclient::size(const string &remotefilename) +{ + if (m_ftpconn == 0) return false; + + m_size=0; + + if (FtpSize(remotefilename.c_str(),&m_size,FTPLIB_IMAGE,m_ftpconn) == false) return false; + + return true; +} + +bool cftpclient::chdir(const string &remotedir) +{ + if (m_ftpconn == 0) return false; + + if (FtpChdir(remotedir.c_str(),m_ftpconn) == false) return false; + + return true; +} + +bool cftpclient::mkdir(const string &remotedir) +{ + if (m_ftpconn == 0) return false; + + if (FtpMkdir(remotedir.c_str(),m_ftpconn) == false) return false; + + return true; +} + +bool cftpclient::rmdir(const string &remotedir) +{ + if (m_ftpconn == 0) return false; + + if (FtpRmdir(remotedir.c_str(),m_ftpconn) == false) return false; + + return true; +} + +bool cftpclient::nlist(const string &remotedir,const string &listfilename) +{ + if (m_ftpconn == 0) return false; + + newdir(listfilename.c_str()); // 创建本地list文件目录 + + if (FtpNlst(listfilename.c_str(),remotedir.c_str(),m_ftpconn) == false) return false; + + return true; +} + +bool cftpclient::put(const string &localfilename,const string &remotefilename,const bool bchecksize) +{ + if (m_ftpconn == 0) return false; + + // 生成服务器文件的临时文件名。 + string strremotefilenametmp=remotefilename+".tmp"; + + string filetime1,filetime2; + filemtime(localfilename,filetime1); // 获取上传文件之前的时间。 + + // 发送文件。 + if (FtpPut(localfilename.c_str(),strremotefilenametmp.c_str(),FTPLIB_IMAGE,m_ftpconn) == false) return false; + + filemtime(localfilename,filetime2); // 获取上传文件之后的时间。 + + // 如果文件上传前后的时间不一致,说明本地有修改文件,放弃本次上传。 + if (filetime1!=filetime2) { ftpdelete(strremotefilenametmp); return false; } + + // 重命名文件。 + if (FtpRename(strremotefilenametmp.c_str(),remotefilename.c_str(),m_ftpconn) == false) return false; + + // 判断已上传的文件的大小与本地文件是否相同,确保上传成功。 + // 一般来说,不会出现文件大小不一致的情况,如果有,应该是服务器方的原因,不太好处理。 + if (bchecksize==true) + { + if (size(remotefilename) == false) return false; + + if (m_size != filesize(localfilename)) { ftpdelete(remotefilename); return false; } + } + + return true; +} + +bool cftpclient::ftpdelete(const string &remotefilename) +{ + if (m_ftpconn == 0) return false; + + if (FtpDelete(remotefilename.c_str(),m_ftpconn) == false) return false; + + return true; +} + +bool cftpclient::ftprename(const string &srcremotefilename,const string &dstremotefilename) +{ + if (m_ftpconn == 0) return false; + + if (FtpRename(srcremotefilename.c_str(),dstremotefilename.c_str(),m_ftpconn) == false) return false; + + return true; +} + +bool cftpclient::site(const string &command) +{ + if (m_ftpconn == 0) return false; + + if (FtpSite(command.c_str(),m_ftpconn) == false) return false; + + return true; +} + +char *cftpclient::response() +{ + if (m_ftpconn == 0) return 0; + + return FtpLastResponse(m_ftpconn); +} + +} // end namespace idc \ No newline at end of file diff --git a/EL/_ftp.h b/EL/_ftp.h new file mode 100644 index 0000000..4a70773 --- /dev/null +++ b/EL/_ftp.h @@ -0,0 +1,116 @@ +/****************************************************************************************/ +/* 程序名:_ftp.h,此程序是开发框架的ftp客户端工具的类的声明文件。 */ +/****************************************************************************************/ + +#ifndef __FTP_H +#define __FTP_H + +#include "_el.h" +#include "ftplib.h" + +namespace eviwbh +{ + +class cftpclient +{ +private: + netbuf *m_ftpconn; // ftp连接句柄。 +public: + unsigned int m_size; // 文件的大小,单位:字节。 + string m_mtime; // 文件的修改时间,格式:yyyymmddhh24miss。 + + // 以下三个成员变量用于存放login方法登录失败的原因。 + bool m_connectfailed; // 如果网络连接失败,该成员的值为true。 + bool m_loginfailed; // 如果登录失败,用户名和密码不正确,或没有登录权限,该成员的值为true。 + bool m_optionfailed; // 如果设置传输模式失败,该成员变量的值为true。 + + cftpclient(); // 类的构造函数。 + ~cftpclient(); // 类的析构函数。 + + cftpclient(const cftpclient&) = delete; + cftpclient& operator=(const cftpclient) = delete; + + void initdata(); // 初始化m_size和m_mtime成员变量。 + + // 登录ftp服务器。 + // host:ftp服务器ip地址和端口,中间用":"分隔,如"192.168.1.1:21"。 + // username:登录ftp服务器用户名。 + // password:登录ftp服务器的密码。 + // imode:传输模式,1-FTPLIB_PASSIVE是被动模式,2-FTPLIB_PORT是主动模式,缺省是被动模式。 + bool login(const string &host,const string &username,const string &password,const int imode=FTPLIB_PASSIVE); + + // 注销。 + bool logout(); + + // 获取ftp服务器上文件的时间。 + // remotefilename:待获取的文件名。 + // 返回值:false-失败;true-成功,获取到的文件时间存放在m_mtime成员变量中。 + bool mtime(const string &remotefilename); + + // 获取ftp服务器上文件的大小。 + // remotefilename:待获取的文件名。 + // 返回值:false-失败;true-成功,获取到的文件大小存放在m_size成员变量中。 + bool size(const string &remotefilename); + + // 改变ftp服务器的当前工作目录。 + // remotedir:ftp服务器上的目录名。 + // 返回值:true-成功;false-失败。 + bool chdir(const string &remotedir); + + // 在ftp服务器上创建目录。 + // remotedir:ftp服务器上待创建的目录名。 + // 返回值:true-成功;false-失败。 + bool mkdir(const string &remotedir); + + // 删除ftp服务器上的目录。 + // remotedir:ftp服务器上待删除的目录名。 + // 返回值:true-成功;如果权限不足、目录不存在或目录不为空会返回false。 + bool rmdir(const string &remotedir); + + // 发送NLST命令列出ftp服务器目录中的子目录名和文件名。 + // remotedir:ftp服务器的目录名。 + // listfilename:用于保存从服务器返回的目录和文件名列表。 + // 返回值:true-成功;false-失败。 + // 注意:如果列出的是ftp服务器当前目录,remotedir用"","*","."都可以,但是,不规范的ftp服务器可能有差别。 + bool nlist(const string &remotedir,const string &listfilename); + + // 从ftp服务器上获取文件。 + // remotefilename:待获取ftp服务器上的文件名。 + // localfilename:保存到本地的文件名。 + // bcheckmtime:文件传输完成后,是否核对远程文件传输前后的时间,保证文件的完整性。 + // 返回值:true-成功;false-失败。 + // 注意:文件在传输的过程中,采用临时文件命名的方法,即在localfilename后加".tmp",在传输 + // 完成后才正式改为localfilename。 + bool get(const string &remotefilename,const string &localfilename,const bool bcheckmtime=true); + + // 向ftp服务器发送文件。 + // localfilename:本地待发送的文件名。 + // remotefilename:发送到ftp服务器上的文件名。 + // bchecksize:文件传输完成后,是否核对本地文件和远程文件的大小,保证文件的完整性。 + // 返回值:true-成功;false-失败。 + // 注意:文件在传输的过程中,采用临时文件命名的方法,即在remotefilename后加".tmp",在传输 + // 完成后才正式改为remotefilename。 + bool put(const string &localfilename,const string &remotefilename,const bool bchecksize=true); + + // 删除ftp服务器上的文件。 + // remotefilename:待删除的ftp服务器上的文件名。 + // 返回值:true-成功;false-失败。 + bool ftpdelete(const string &remotefilename); + + // 重命名ftp服务器上的文件。 + // srcremotefilename:ftp服务器上的原文件名。 + // dstremotefilename:ftp服务器上的目标文件名。 + // 返回值:true-成功;false-失败。 + bool ftprename(const string &srcremotefilename,const string &dstremotefilename); + + // 向ftp服务器发送site命令。 + // command:命令的内容。 + // 返回值:true-成功;false-失败。 + bool site(const string &command); + + // 获取服务器返回信息的最后一条(return a pointer to the last response received)。 + char *response(); +}; + +} // end namespace idc +#endif diff --git a/EL/ftplib.c b/EL/ftplib.c new file mode 100644 index 0000000..c13fb54 --- /dev/null +++ b/EL/ftplib.c @@ -0,0 +1,1455 @@ +/***************************************************************************/ +/* */ +/* ftplib.c - callable ftp access routines */ +/* Copyright (C) 1996-2001, 2013, 2016 Thomas Pfau, tfpfau@gmail.com */ +/* 1407 Thomas Ave, North Brunswick, NJ, 08902 */ +/* */ +/* This library is free software. You can redistribute it and/or */ +/* modify it under the terms of the Artistic License 2.0. */ +/* */ +/* This library is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* Artistic License 2.0 for more details. */ +/* */ +/* See the file LICENSE or */ +/* http://www.perlfoundation.org/artistic_license_2_0 */ +/* */ +/***************************************************************************/ + +#if defined(__unix__) || defined(__VMS) +#include +#endif +#if defined(_WIN32) +#include +#endif +#include +#include +#include +#include +#include +#if defined(__unix__) +#include +#include +#include +#include +#include +#include +#elif defined(VMS) +#include +#include +#include +#include +#include +#elif defined(_WIN32) +#include +#endif +#if defined(__APPLE__) +#undef _REENTRANT +#endif + +#define BUILDING_LIBRARY +#include "ftplib.h" + +#if defined(__UINT64_MAX) && !defined(PRIu64) +#if ULONG_MAX == __UINT32_MAX +#define PRIu64 "llu" +#else +#define PRIu64 "lu" +#endif +#endif + +#if defined(_WIN32) +#define SETSOCKOPT_OPTVAL_TYPE (const char *) +#else +#define SETSOCKOPT_OPTVAL_TYPE (void *) +#endif + +#define FTPLIB_BUFSIZ 8192 +#define RESPONSE_BUFSIZ 1024 +#define TMP_BUFSIZ 1024 +#define ACCEPT_TIMEOUT 30 + +#define FTPLIB_CONTROL 0 +#define FTPLIB_READ 1 +#define FTPLIB_WRITE 2 + +#if !defined FTPLIB_DEFMODE +#define FTPLIB_DEFMODE FTPLIB_PASSIVE +#endif + +struct NetBuf { + char *cput,*cget; + int handle; // 用于通讯的socket + int cavail,cleft; + char *buf; + int dir; + netbuf *ctrl; + netbuf *data; + int cmode; + struct timeval idletime; + FtpCallback idlecb; + void *idlearg; + unsigned long int xfered; + unsigned long int cbbytes; + unsigned long int xfered1; + char response[RESPONSE_BUFSIZ]; +}; + +static char *version = + (char*)"ftplib Release 4.0 07-Jun-2013, copyright 1996-2003, 2013 Thomas Pfau"; + +GLOBALDEF int ftplib_debug = 0; + +#if defined(__unix__) || defined(VMS) +int net_read(int fd, char *buf, size_t len) +{ + while ( 1 ) + { + int c = read(fd, buf, len); + if ( c == -1 ) + { + if ( errno != EINTR && errno != EAGAIN ) + return -1; + } + else + { + return c; + } + } +} + +int net_write(int fd, const char *buf, size_t len) +{ + int done = 0; + while ( len > 0 ) + { + int c = write( fd, buf, len ); + if ( c == -1 ) + { + if ( errno != EINTR && errno != EAGAIN ) + return -1; + } + else if ( c == 0 ) + { + return done; + } + else + { + buf += c; + done += c; + len -= c; + } + } + return done; +} +#define net_close close +#elif defined(_WIN32) +#define net_read(x,y,z) recv(x,y,z,0) +#define net_write(x,y,z) send(x,y,z,0) +#define net_close closesocket +#endif + +#if defined(NEED_MEMCCPY) +/* + * VAX C does not supply a memccpy routine so I provide my own + */ +void *memccpy(void *dest, const void *src, int c, size_t n) +{ + int i=0; + const unsigned char *ip=src; + unsigned char *op=dest; + + while (i < n) + { + if ((*op++ = *ip++) == c) + break; + i++; + } + if (i == n) + return NULL; + return op; +} +#endif +#if defined(NEED_STRDUP) +/* + * strdup - return a malloc'ed copy of a string + */ +char *strdup(const char *src) +{ + int l = strlen(src) + 1; + char *dst = malloc(l); + if (dst) + strcpy(dst,src); + return dst; +} +#endif + +/* + * socket_wait - wait for socket to receive or flush data + * + * return 1 if no user callback, otherwise, return value returned by + * user callback + */ +static int socket_wait(netbuf *ctl) +{ + fd_set fd,*rfd = NULL,*wfd = NULL; + struct timeval tv; + int rv = 0; + if ((ctl->dir == FTPLIB_CONTROL) || (ctl->idlecb == NULL)) + return 1; + if (ctl->dir == FTPLIB_WRITE) + wfd = &fd; + else + rfd = &fd; + FD_ZERO(&fd); + do + { + FD_SET(ctl->handle,&fd); + tv = ctl->idletime; + rv = select(ctl->handle+1, rfd, wfd, NULL, &tv); + if (rv == -1) + { + rv = 0; + strncpy(ctl->ctrl->response, strerror(errno), + sizeof(ctl->ctrl->response)); + break; + } + else if (rv > 0) + { + rv = 1; + break; + } + } + while ((rv = ctl->idlecb(ctl, ctl->xfered, ctl->idlearg))); + return rv; +} + +/* + * read a line of text + * + * return -1 on error or bytecount + */ +static int readline(char *buf,int max,netbuf *ctl) +{ + int x,retval = 0; + char *end,*bp=buf; + int eof = 0; + + if ((ctl->dir != FTPLIB_CONTROL) && (ctl->dir != FTPLIB_READ)) + return -1; + if (max == 0) + return 0; + do + { + if (ctl->cavail > 0) + { + x = (max >= ctl->cavail) ? ctl->cavail : max-1; + end = memccpy(bp,ctl->cget,'\n',x); + if (end != NULL) + x = end - bp; + retval += x; + bp += x; + *bp = '\0'; + max -= x; + ctl->cget += x; + ctl->cavail -= x; + if (end != NULL) + { + bp -= 2; + if (strcmp(bp,"\r\n") == 0) + { + *bp++ = '\n'; + *bp++ = '\0'; + --retval; + } + break; + } + } + if (max == 1) + { + *buf = '\0'; + break; + } + if (ctl->cput == ctl->cget) + { + ctl->cput = ctl->cget = ctl->buf; + ctl->cavail = 0; + ctl->cleft = FTPLIB_BUFSIZ; + } + if (eof) + { + if (retval == 0) + retval = -1; + break; + } + if (!socket_wait(ctl)) + return retval; + if ((x = net_read(ctl->handle,ctl->cput,ctl->cleft)) == -1) + { + if (ftplib_debug) + perror("read"); + retval = -1; + break; + } + if (x == 0) + eof = 1; + ctl->cleft -= x; + ctl->cavail += x; + ctl->cput += x; + } + while (1); + return retval; +} + +/* + * write lines of text + * + * return -1 on error or bytecount + */ +static int writeline(const char *buf, int len, netbuf *nData) +{ + int x, nb=0, w; + const char *ubp = buf; + char *nbp; + char lc=0; + + if (nData->dir != FTPLIB_WRITE) + return -1; + nbp = nData->buf; + for (x=0; x < len; x++) + { + if ((*ubp == '\n') && (lc != '\r')) + { + if (nb == FTPLIB_BUFSIZ) + { + if (!socket_wait(nData)) + return x; + w = net_write(nData->handle, nbp, FTPLIB_BUFSIZ); + if (w != FTPLIB_BUFSIZ) + { + if (ftplib_debug) + printf("net_write(1) returned %d, errno = %d\n", w, errno); + return(-1); + } + nb = 0; + } + nbp[nb++] = '\r'; + } + if (nb == FTPLIB_BUFSIZ) + { + if (!socket_wait(nData)) + return x; + w = net_write(nData->handle, nbp, FTPLIB_BUFSIZ); + if (w != FTPLIB_BUFSIZ) + { + if (ftplib_debug) + printf("net_write(2) returned %d, errno = %d\n", w, errno); + return(-1); + } + nb = 0; + } + nbp[nb++] = lc = *ubp++; + } + if (nb) + { + if (!socket_wait(nData)) + return x; + w = net_write(nData->handle, nbp, nb); + if (w != nb) + { + if (ftplib_debug) + printf("net_write(3) returned %d, errno = %d\n", w, errno); + return(-1); + } + } + return len; +} + +/* + * read a response from the server + * + * return 0 if first char doesn't match + * return 1 if first char matches + */ +static int readresp(char c, netbuf *nControl) +{ + char match[5]; + if (readline(nControl->response,RESPONSE_BUFSIZ,nControl) == -1) + { + if (ftplib_debug) + perror("Control socket read failed"); + return 0; + } + if (ftplib_debug > 1) + fprintf(stderr,"%s",nControl->response); + if (nControl->response[3] == '-') + { + strncpy(match,nControl->response,3); + match[3] = ' '; + match[4] = '\0'; + do + { + if (readline(nControl->response,RESPONSE_BUFSIZ,nControl) == -1) + { + if (ftplib_debug) + perror("Control socket read failed"); + return 0; + } + if (ftplib_debug > 1) + fprintf(stderr,"%s",nControl->response); + } + while (strncmp(nControl->response,match,4)); + } + if (nControl->response[0] == c) + return 1; + return 0; +} + +/* + * FtpInit for stupid operating systems that require it (Windows NT) + */ +GLOBALDEF void FtpInit(void) +{ +#if defined(_WIN32) + WORD wVersionRequested; + WSADATA wsadata; + int err; + wVersionRequested = MAKEWORD(1,1); + if ((err = WSAStartup(wVersionRequested,&wsadata)) != 0) + fprintf(stderr,"Network failed to start: %d\n",err); +#endif +} + +/* + * FtpLastResponse - return a pointer to the last response received + */ +GLOBALDEF char *FtpLastResponse(netbuf *nControl) +{ + if ((nControl) && (nControl->dir == FTPLIB_CONTROL)) + return nControl->response; + return NULL; +} + +/* + * FtpConnect - connect to remote server + * + * return 1 if connected, 0 if not + */ +GLOBALDEF int FtpConnect(const char *host, netbuf **nControl) +{ + int sControl; + struct sockaddr_in sin; + int on=1; + netbuf *ctrl; + char *lhost; + char *pnum; + + memset(&sin,0,sizeof(sin)); + sin.sin_family = AF_INET; + lhost = strdup(host); + pnum = strchr(lhost,':'); + if (pnum == NULL) + pnum = "ftp"; + else + *pnum++ = '\0'; + if (isdigit(*pnum)) + sin.sin_port = htons(atoi(pnum)); + else + { + struct servent *pse; +#if _REENTRANT + struct servent se; + char tmpbuf[TMP_BUFSIZ]; + int i; + if ( ( i = getservbyname_r(pnum,"tcp",&se,tmpbuf,TMP_BUFSIZ,&pse) ) != 0 ) + { + errno = i; + if ( ftplib_debug ) + perror("getservbyname_r"); + free(lhost); + return 0; + } +#else + if ((pse = getservbyname(pnum,"tcp") ) == NULL ) + { + if ( ftplib_debug ) + perror("getservbyname"); + free(lhost); + return 0; + } +#endif + sin.sin_port = pse->s_port; + } + if ((sin.sin_addr.s_addr = inet_addr(lhost)) == INADDR_NONE) + { + struct hostent *phe; +#ifdef _REENTRANT + struct hostent he; + char tmpbuf[TMP_BUFSIZ]; + int i, herr; + if ( ( ( i = gethostbyname_r( lhost, &he, tmpbuf, TMP_BUFSIZ, &phe, &herr ) ) != 0 ) || + ( phe == NULL ) ) + { + if ( ftplib_debug ) + fprintf(stderr, "gethostbyname: %s\n", hstrerror(herr)); + free(lhost); + return 0; + } +#else + if ((phe = gethostbyname(lhost)) == NULL) + { + if (ftplib_debug) + fprintf(stderr, "gethostbyname: %s\n", hstrerror(h_errno)); + free(lhost); + return 0; + } +#endif + memcpy((char *)&sin.sin_addr, phe->h_addr, phe->h_length); + } + free(lhost); + sControl = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sControl == -1) + { + if (ftplib_debug) + perror("socket"); + return 0; + } + if (setsockopt(sControl,SOL_SOCKET,SO_REUSEADDR, + SETSOCKOPT_OPTVAL_TYPE &on, sizeof(on)) == -1) + { + if (ftplib_debug) + perror("setsockopt"); + net_close(sControl); + return 0; + } + if (connect(sControl, (struct sockaddr *)&sin, sizeof(sin)) == -1) + { + if (ftplib_debug) + perror("connect"); + net_close(sControl); + return 0; + } + ctrl = calloc(1,sizeof(netbuf)); + if (ctrl == NULL) + { + if (ftplib_debug) + perror("calloc"); + net_close(sControl); + return 0; + } + ctrl->buf = malloc(FTPLIB_BUFSIZ); + if (ctrl->buf == NULL) + { + if (ftplib_debug) + perror("calloc"); + net_close(sControl); + free(ctrl); + return 0; + } + ctrl->handle = sControl; + ctrl->dir = FTPLIB_CONTROL; + ctrl->ctrl = NULL; + ctrl->data = NULL; + ctrl->cmode = FTPLIB_DEFMODE; + ctrl->idlecb = NULL; + ctrl->idletime.tv_sec = ctrl->idletime.tv_usec = 0; + ctrl->idlearg = NULL; + ctrl->xfered = 0; + ctrl->xfered1 = 0; + ctrl->cbbytes = 0; + if (readresp('2', ctrl) == 0) + { + net_close(sControl); + free(ctrl->buf); + free(ctrl); + return 0; + } + *nControl = ctrl; + return 1; +} + +GLOBALDEF int FtpSetCallback(const FtpCallbackOptions *opt, netbuf *nControl) +{ + nControl->idlecb = opt->cbFunc; + nControl->idlearg = opt->cbArg; + nControl->idletime.tv_sec = opt->idleTime / 1000; + nControl->idletime.tv_usec = (opt->idleTime % 1000) * 1000; + nControl->cbbytes = opt->bytesXferred; + return 1; +} +GLOBALDEF int FtpClearCallback(netbuf *nControl) +{ + nControl->idlecb = NULL; + nControl->idlearg = NULL; + nControl->idletime.tv_sec = 0; + nControl->idletime.tv_usec = 0; + nControl->cbbytes = 0; + return 1; +} +/* + * FtpOptions - change connection options + * + * returns 1 if successful, 0 on error + */ +GLOBALDEF int FtpOptions(int opt, long val, netbuf *nControl) +{ + int v,rv=0; + switch (opt) + { + case FTPLIB_CONNMODE: + v = (int) val; + if ((v == FTPLIB_PASSIVE) || (v == FTPLIB_PORT)) + { + nControl->cmode = v; + rv = 1; + } + break; + case FTPLIB_CALLBACK: + nControl->idlecb = (FtpCallback) val; + rv = 1; + break; + case FTPLIB_IDLETIME: + v = (int) val; + rv = 1; + nControl->idletime.tv_sec = v / 1000; + nControl->idletime.tv_usec = (v % 1000) * 1000; + break; + case FTPLIB_CALLBACKARG: + rv = 1; + nControl->idlearg = (void *) val; + break; + case FTPLIB_CALLBACKBYTES: + rv = 1; + nControl->cbbytes = (int) val; + break; + } + return rv; +} + +/* + * FtpSendCmd - send a command and wait for expected response + * + * return 1 if proper response received, 0 otherwise + */ +static int FtpSendCmd(const char *cmd, char expresp, netbuf *nControl) +{ + char buf[TMP_BUFSIZ]; + if (nControl->dir != FTPLIB_CONTROL) + return 0; + if (ftplib_debug > 2) + fprintf(stderr,"%s\n",cmd); + if ((strlen(cmd) + 3) > sizeof(buf)) + return 0; + sprintf(buf,"%s\r\n",cmd); + if (net_write(nControl->handle,buf,strlen(buf)) <= 0) + { + if (ftplib_debug) + perror("write"); + return 0; + } + return readresp(expresp, nControl); +} + +/* + * FtpLogin - log in to remote server + * + * return 1 if logged in, 0 otherwise + */ +GLOBALDEF int FtpLogin(const char *user, const char *pass, netbuf *nControl) +{ + char tempbuf[64]; + + if (((strlen(user) + 7) > sizeof(tempbuf)) || + ((strlen(pass) + 7) > sizeof(tempbuf))) + return 0; + sprintf(tempbuf,"USER %s",user); + if (!FtpSendCmd(tempbuf,'3',nControl)) + { + if (nControl->response[0] == '2') + return 1; + return 0; + } + sprintf(tempbuf,"PASS %s",pass); + return FtpSendCmd(tempbuf,'2',nControl); +} + +/* + * FtpOpenPort - set up data connection + * + * return 1 if successful, 0 otherwise + */ +static int FtpOpenPort(netbuf *nControl, netbuf **nData, int mode, int dir) +{ + int sData; + union { + struct sockaddr sa; + struct sockaddr_in in; + } sin; + struct linger lng = { 0, 0 }; + unsigned int l; + int on=1; + netbuf *ctrl; + char *cp; + unsigned int v[6]; + char buf[TMP_BUFSIZ]; + + if (nControl->dir != FTPLIB_CONTROL) + return -1; + if ((dir != FTPLIB_READ) && (dir != FTPLIB_WRITE)) + { + sprintf(nControl->response, "Invalid direction %d\n", dir); + return -1; + } + if ((mode != FTPLIB_ASCII) && (mode != FTPLIB_IMAGE)) + { + sprintf(nControl->response, "Invalid mode %c\n", mode); + return -1; + } + l = sizeof(sin); + if (nControl->cmode == FTPLIB_PASSIVE) + { + memset(&sin, 0, l); + sin.in.sin_family = AF_INET; + if (!FtpSendCmd("PASV",'2',nControl)) + return -1; + cp = strchr(nControl->response,'('); + if (cp == NULL) + return -1; + cp++; + sscanf(cp,"%u,%u,%u,%u,%u,%u",&v[2],&v[3],&v[4],&v[5],&v[0],&v[1]); + sin.sa.sa_data[2] = v[2]; + sin.sa.sa_data[3] = v[3]; + sin.sa.sa_data[4] = v[4]; + sin.sa.sa_data[5] = v[5]; + sin.sa.sa_data[0] = v[0]; + sin.sa.sa_data[1] = v[1]; + } + else + { + if (getsockname(nControl->handle, &sin.sa, &l) < 0) + { + if (ftplib_debug) + perror("getsockname"); + return -1; + } + } + sData = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP); + if (sData == -1) + { + if (ftplib_debug) + perror("socket"); + return -1; + } + if (setsockopt(sData,SOL_SOCKET,SO_REUSEADDR, + SETSOCKOPT_OPTVAL_TYPE &on,sizeof(on)) == -1) + { + if (ftplib_debug) + perror("setsockopt"); + net_close(sData); + return -1; + } + if (setsockopt(sData,SOL_SOCKET,SO_LINGER, + SETSOCKOPT_OPTVAL_TYPE &lng,sizeof(lng)) == -1) + { + if (ftplib_debug) + perror("setsockopt"); + net_close(sData); + return -1; + } + if (nControl->cmode == FTPLIB_PASSIVE) + { + if (connect(sData, &sin.sa, sizeof(sin.sa)) == -1) + { + if (ftplib_debug) + perror("connect"); + net_close(sData); + return -1; + } + } + else + { + sin.in.sin_port = 0; + if (bind(sData, &sin.sa, sizeof(sin)) == -1) + { + if (ftplib_debug) + perror("bind"); + net_close(sData); + return -1; + } + if (listen(sData, 1) < 0) + { + if (ftplib_debug) + perror("listen"); + net_close(sData); + return -1; + } + if (getsockname(sData, &sin.sa, &l) < 0) + return -1; + sprintf(buf, "PORT %d,%d,%d,%d,%d,%d", + (unsigned char) sin.sa.sa_data[2], + (unsigned char) sin.sa.sa_data[3], + (unsigned char) sin.sa.sa_data[4], + (unsigned char) sin.sa.sa_data[5], + (unsigned char) sin.sa.sa_data[0], + (unsigned char) sin.sa.sa_data[1]); + if (!FtpSendCmd(buf,'2',nControl)) + { + net_close(sData); + return -1; + } + } + ctrl = calloc(1,sizeof(netbuf)); + if (ctrl == NULL) + { + if (ftplib_debug) + perror("calloc"); + net_close(sData); + return -1; + } + if ((mode == 'A') && ((ctrl->buf = malloc(FTPLIB_BUFSIZ)) == NULL)) + { + if (ftplib_debug) + perror("calloc"); + net_close(sData); + free(ctrl); + return -1; + } + ctrl->handle = sData; + ctrl->dir = dir; + ctrl->idletime = nControl->idletime; + ctrl->idlearg = nControl->idlearg; + ctrl->xfered = 0; + ctrl->xfered1 = 0; + ctrl->cbbytes = nControl->cbbytes; + ctrl->ctrl = nControl; + if (ctrl->idletime.tv_sec || ctrl->idletime.tv_usec || ctrl->cbbytes) + ctrl->idlecb = nControl->idlecb; + else + ctrl->idlecb = NULL; + nControl->data = ctrl; + *nData = ctrl; + return 1; +} + +/* + * FtpAcceptConnection - accept connection from server + * + * return 1 if successful, 0 otherwise + */ +static int FtpAcceptConnection(netbuf *nData, netbuf *nControl) +{ + int sData; + struct sockaddr addr; + unsigned int l; + int i; + struct timeval tv; + fd_set mask; + int rv; + + FD_ZERO(&mask); + FD_SET(nControl->handle, &mask); + FD_SET(nData->handle, &mask); + tv.tv_usec = 0; + tv.tv_sec = ACCEPT_TIMEOUT; + i = nControl->handle; + if (i < nData->handle) + i = nData->handle; + i = select(i+1, &mask, NULL, NULL, &tv); + if (i == -1) + { + strncpy(nControl->response, strerror(errno), + sizeof(nControl->response)); + net_close(nData->handle); + nData->handle = 0; + rv = 0; + } + else if (i == 0) + { + strcpy(nControl->response, "timed out waiting for connection"); + net_close(nData->handle); + nData->handle = 0; + rv = 0; + } + else + { + if (FD_ISSET(nData->handle, &mask)) + { + l = sizeof(addr); + sData = accept(nData->handle, &addr, &l); + i = errno; + net_close(nData->handle); + if (sData > 0) + { + rv = 1; + nData->handle = sData; + } + else + { + strncpy(nControl->response, strerror(i), + sizeof(nControl->response)); + nData->handle = 0; + rv = 0; + } + } + else if (FD_ISSET(nControl->handle, &mask)) + { + net_close(nData->handle); + nData->handle = 0; + readresp('2', nControl); + rv = 0; + } + } + return rv; +} + +/* + * FtpAccess - return a handle for a data stream + * + * return 1 if successful, 0 otherwise + */ +GLOBALDEF int FtpAccess(const char *path, int typ, int mode, netbuf *nControl, + netbuf **nData) +{ + char buf[TMP_BUFSIZ]; + int dir; + if ((path == NULL) && + ((typ == FTPLIB_FILE_WRITE) || (typ == FTPLIB_FILE_READ))) + { + sprintf(nControl->response, + "Missing path argument for file transfer\n"); + return 0; + } + sprintf(buf, "TYPE %c", mode); + if (!FtpSendCmd(buf, '2', nControl)) + return 0; + switch (typ) + { + case FTPLIB_DIR: + strcpy(buf,"NLST"); + dir = FTPLIB_READ; + break; + case FTPLIB_DIR_VERBOSE: + strcpy(buf,"LIST"); + dir = FTPLIB_READ; + break; + case FTPLIB_FILE_READ: + strcpy(buf,"RETR"); + dir = FTPLIB_READ; + break; + case FTPLIB_FILE_WRITE: + strcpy(buf,"STOR"); + dir = FTPLIB_WRITE; + break; + default: + sprintf(nControl->response, "Invalid open type %d\n", typ); + return 0; + } + if (path != NULL) + { + int i = strlen(buf); + buf[i++] = ' '; + if ((strlen(path) + i + 1) >= sizeof(buf)) + return 0; + strcpy(&buf[i],path); + } + if (FtpOpenPort(nControl, nData, mode, dir) == -1) + return 0; + if (!FtpSendCmd(buf, '1', nControl)) + { + FtpClose(*nData); + *nData = NULL; + return 0; + } + if (nControl->cmode == FTPLIB_PORT) + { + if (!FtpAcceptConnection(*nData,nControl)) + { + FtpClose(*nData); + *nData = NULL; + nControl->data = NULL; + return 0; + } + } + return 1; +} + +/* + * FtpRead - read from a data connection + */ +GLOBALDEF int FtpRead(void *buf, int max, netbuf *nData) +{ + int i; + if (nData->dir != FTPLIB_READ) + return 0; + if (nData->buf) + i = readline(buf, max, nData); + else + { + i = socket_wait(nData); + if (i != 1) + return 0; + i = net_read(nData->handle, buf, max); + } + if (i == -1) + return 0; + nData->xfered += i; + if (nData->idlecb && nData->cbbytes) + { + nData->xfered1 += i; + if (nData->xfered1 > nData->cbbytes) + { + if (nData->idlecb(nData, nData->xfered, nData->idlearg) == 0) + return 0; + nData->xfered1 = 0; + } + } + return i; +} + +/* + * FtpWrite - write to a data connection + */ +GLOBALDEF int FtpWrite(const void *buf, int len, netbuf *nData) +{ + int i; + if (nData->dir != FTPLIB_WRITE) + return 0; + if (nData->buf) + i = writeline(buf, len, nData); + else + { + socket_wait(nData); + i = net_write(nData->handle, buf, len); + } + if (i == -1) + return 0; + nData->xfered += i; + if (nData->idlecb && nData->cbbytes) + { + nData->xfered1 += i; + if (nData->xfered1 > nData->cbbytes) + { + nData->idlecb(nData, nData->xfered, nData->idlearg); + nData->xfered1 = 0; + } + } + return i; +} + +/* + * FtpClose - close a data connection + */ +GLOBALDEF int FtpClose(netbuf *nData) +{ + netbuf *ctrl; + switch (nData->dir) + { + case FTPLIB_WRITE: + /* potential problem - if buffer flush fails, how to notify user? */ + if (nData->buf != NULL) + writeline(NULL, 0, nData); + case FTPLIB_READ: + if (nData->buf) + free(nData->buf); + shutdown(nData->handle,2); + net_close(nData->handle); + ctrl = nData->ctrl; + free(nData); + ctrl->data = NULL; + if (ctrl && ctrl->response[0] != '4' && ctrl->response[0] != '5') + { + return(readresp('2', ctrl)); + } + return 1; + case FTPLIB_CONTROL: + if (nData->data) + { + nData->ctrl = NULL; + FtpClose(nData->data); + } + net_close(nData->handle); + free(nData); + return 0; + } + return 1; +} + +/* + * FtpSite - send a SITE command + * + * return 1 if command successful, 0 otherwise + */ +GLOBALDEF int FtpSite(const char *cmd, netbuf *nControl) +{ + char buf[TMP_BUFSIZ]; + + if ((strlen(cmd) + 7) > sizeof(buf)) + return 0; + sprintf(buf,"SITE %s",cmd); + if (!FtpSendCmd(buf,'2',nControl)) + return 0; + return 1; +} + +/* + * FtpSysType - send a SYST command + * + * Fills in the user buffer with the remote system type. If more + * information from the response is required, the user can parse + * it out of the response buffer returned by FtpLastResponse(). + * + * return 1 if command successful, 0 otherwise + */ +GLOBALDEF int FtpSysType(char *buf, int max, netbuf *nControl) +{ + int l = max; + char *b = buf; + char *s; + if (!FtpSendCmd("SYST",'2',nControl)) + return 0; + s = &nControl->response[4]; + while ((--l) && (*s != ' ')) + *b++ = *s++; + *b++ = '\0'; + return 1; +} + +/* + * FtpMkdir - create a directory at server + * + * return 1 if successful, 0 otherwise + */ +GLOBALDEF int FtpMkdir(const char *path, netbuf *nControl) +{ + char buf[TMP_BUFSIZ]; + + if ((strlen(path) + 6) > sizeof(buf)) + return 0; + sprintf(buf,"MKD %s",path); + if (!FtpSendCmd(buf,'2', nControl)) + return 0; + return 1; +} + +/* + * FtpChdir - change path at remote + * + * return 1 if successful, 0 otherwise + */ +GLOBALDEF int FtpChdir(const char *path, netbuf *nControl) +{ + char buf[TMP_BUFSIZ]; + + if ((strlen(path) + 6) > sizeof(buf)) + return 0; + sprintf(buf,"CWD %s",path); + if (!FtpSendCmd(buf,'2',nControl)) + return 0; + return 1; +} + +/* + * FtpCDUp - move to parent directory at remote + * + * return 1 if successful, 0 otherwise + */ +GLOBALDEF int FtpCDUp(netbuf *nControl) +{ + if (!FtpSendCmd("CDUP",'2',nControl)) + return 0; + return 1; +} + +/* + * FtpRmdir - remove directory at remote + * + * return 1 if successful, 0 otherwise + */ +GLOBALDEF int FtpRmdir(const char *path, netbuf *nControl) +{ + char buf[TMP_BUFSIZ]; + + if ((strlen(path) + 6) > sizeof(buf)) + return 0; + sprintf(buf,"RMD %s",path); + if (!FtpSendCmd(buf,'2',nControl)) + return 0; + return 1; +} + +/* + * FtpPwd - get working directory at remote + * + * return 1 if successful, 0 otherwise + */ +GLOBALDEF int FtpPwd(char *path, int max, netbuf *nControl) +{ + int l = max; + char *b = path; + char *s; + if (!FtpSendCmd("PWD",'2',nControl)) + return 0; + s = strchr(nControl->response, '"'); + if (s == NULL) + return 0; + s++; + while ((--l) && (*s) && (*s != '"')) + *b++ = *s++; + *b++ = '\0'; + return 1; +} + +/* + * FtpXfer - issue a command and transfer data + * + * return 1 if successful, 0 otherwise + */ +static int FtpXfer(const char *localfile, const char *path, + netbuf *nControl, int typ, int mode) +{ + int l,c; + char *dbuf; + FILE *local = NULL; + netbuf *nData; + int rv=1; + + if (localfile != NULL) + { + char ac[4]; + memset( ac, 0, sizeof(ac) ); + if (typ == FTPLIB_FILE_WRITE) + ac[0] = 'r'; + else + ac[0] = 'w'; + if (mode == FTPLIB_IMAGE) + ac[1] = 'b'; + local = fopen(localfile, ac); + if (local == NULL) + { + strncpy(nControl->response, strerror(errno), + sizeof(nControl->response)); + return 0; + } + } + if (local == NULL) + local = (typ == FTPLIB_FILE_WRITE) ? stdin : stdout; + if (!FtpAccess(path, typ, mode, nControl, &nData)) + { + if (localfile) + { + fclose(local); + if ( typ == FTPLIB_FILE_READ ) + unlink(localfile); + } + return 0; + } + dbuf = malloc(FTPLIB_BUFSIZ); + if (typ == FTPLIB_FILE_WRITE) + { + while ((l = fread(dbuf, 1, FTPLIB_BUFSIZ, local)) > 0) + { + if ((c = FtpWrite(dbuf, l, nData)) < l) + { + printf("short write: passed %d, wrote %d\n", l, c); + rv = 0; + break; + } + } + } + else + { + while ((l = FtpRead(dbuf, FTPLIB_BUFSIZ, nData)) > 0) + { + if (fwrite(dbuf, 1, l, local) == 0) + { + if (ftplib_debug) + perror("localfile write"); + rv = 0; + break; + } + } + } + free(dbuf); + fflush(local); + if (localfile != NULL) + fclose(local); + FtpClose(nData); + return rv; +} + +/* + * FtpNlst - issue an NLST command and write response to output + * + * return 1 if successful, 0 otherwise + */ +GLOBALDEF int FtpNlst(const char *outputfile, const char *path, + netbuf *nControl) +{ + return FtpXfer(outputfile, path, nControl, FTPLIB_DIR, FTPLIB_ASCII); +} + +/* + * FtpDir - issue a LIST command and write response to output + * + * return 1 if successful, 0 otherwise + */ +GLOBALDEF int FtpDir(const char *outputfile, const char *path, netbuf *nControl) +{ + return FtpXfer(outputfile, path, nControl, FTPLIB_DIR_VERBOSE, FTPLIB_ASCII); +} + +/* + * FtpSize - determine the size of a remote file + * + * return 1 if successful, 0 otherwise + */ +GLOBALDEF int FtpSize(const char *path, unsigned int *size, char mode, netbuf *nControl) +{ + char cmd[TMP_BUFSIZ]; + int resp,rv=1; + unsigned int sz; + + if ((strlen(path) + 7) > sizeof(cmd)) + return 0; + sprintf(cmd, "TYPE %c", mode); + if (!FtpSendCmd(cmd, '2', nControl)) + return 0; + sprintf(cmd,"SIZE %s",path); + if (!FtpSendCmd(cmd,'2',nControl)) + rv = 0; + else + { + if (sscanf(nControl->response, "%d %u", &resp, &sz) == 2) + *size = sz; + else + rv = 0; + } + return rv; +} + +#if defined(__UINT64_MAX) +/* + * FtpSizeLong - determine the size of a remote file + * + * return 1 if successful, 0 otherwise + */ +GLOBALDEF int FtpSizeLong(const char *path, fsz_t *size, char mode, netbuf *nControl) +{ + char cmd[TMP_BUFSIZ]; + int resp,rv=1; + fsz_t sz; + + if ((strlen(path) + 7) > sizeof(cmd)) + return 0; + sprintf(cmd, "TYPE %c", mode); + if (!FtpSendCmd(cmd, '2', nControl)) + return 0; + sprintf(cmd,"SIZE %s",path); + if (!FtpSendCmd(cmd,'2',nControl)) + rv = 0; + else + { + if (sscanf(nControl->response, "%d %" PRIu64 "", &resp, &sz) == 2) + *size = sz; + else + rv = 0; + } + return rv; +} +#endif + +/* + * FtpModDate - determine the modification date of a remote file + * + * return 1 if successful, 0 otherwise + */ +GLOBALDEF int FtpModDate(const char *path, char *dt, int max, netbuf *nControl) +{ + char buf[TMP_BUFSIZ]; + int rv = 1; + + if ((strlen(path) + 7) > sizeof(buf)) + return 0; + sprintf(buf,"MDTM %s",path); + if (!FtpSendCmd(buf,'2',nControl)) + rv = 0; + else + strncpy(dt, &nControl->response[4], max); + return rv; +} + +/* + * FtpGet - issue a GET command and write received data to output + * + * return 1 if successful, 0 otherwise + */ +GLOBALDEF int FtpGet(const char *outputfile, const char *path, + char mode, netbuf *nControl) +{ + return FtpXfer(outputfile, path, nControl, FTPLIB_FILE_READ, mode); +} + +/* + * FtpPut - issue a PUT command and send data from input + * + * return 1 if successful, 0 otherwise + */ +GLOBALDEF int FtpPut(const char *inputfile, const char *path, char mode, + netbuf *nControl) +{ + return FtpXfer(inputfile, path, nControl, FTPLIB_FILE_WRITE, mode); +} + +/* + * FtpRename - rename a file at remote + * + * return 1 if successful, 0 otherwise + */ +GLOBALDEF int FtpRename(const char *src, const char *dst, netbuf *nControl) +{ + char cmd[TMP_BUFSIZ]; + + if (((strlen(src) + 7) > sizeof(cmd)) || + ((strlen(dst) + 7) > sizeof(cmd))) + return 0; + sprintf(cmd,"RNFR %s",src); + if (!FtpSendCmd(cmd,'3',nControl)) + return 0; + sprintf(cmd,"RNTO %s",dst); + if (!FtpSendCmd(cmd,'2',nControl)) + return 0; + return 1; +} + +/* + * FtpDelete - delete a file at remote + * + * return 1 if successful, 0 otherwise + */ +GLOBALDEF int FtpDelete(const char *fnm, netbuf *nControl) +{ + char cmd[TMP_BUFSIZ]; + + if ((strlen(fnm) + 7) > sizeof(cmd)) + return 0; + sprintf(cmd,"DELE %s",fnm); + if (!FtpSendCmd(cmd,'2', nControl)) + return 0; + return 1; +} + +/* + * FtpQuit - disconnect from remote + * + * return 1 if successful, 0 otherwise + */ +GLOBALDEF void FtpQuit(netbuf *nControl) +{ + if (nControl->dir != FTPLIB_CONTROL) + return; + FtpSendCmd("QUIT",'2',nControl); + net_close(nControl->handle); + free(nControl->buf); + free(nControl); +} diff --git a/EL/ftplib.h b/EL/ftplib.h new file mode 100644 index 0000000..82b90a9 --- /dev/null +++ b/EL/ftplib.h @@ -0,0 +1,120 @@ +/***************************************************************************/ +/* */ +/* ftplib.h - header file for callable ftp access routines */ +/* Copyright (C) 1996-2001, 2013, 2016 Thomas Pfau, tfpfau@gmail.com */ +/* 1407 Thomas Ave, North Brunswick, NJ, 08902 */ +/* */ +/* This library is free software. You can redistribute it and/or */ +/* modify it under the terms of the Artistic License 2.0. */ +/* */ +/* This library is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* Artistic License 2.0 for more details. */ +/* */ +/* See the file LICENSE or */ +/* http://www.perlfoundation.org/artistic_license_2_0 */ +/* */ +/***************************************************************************/ + +#if !defined(__FTPLIB_H) +#define __FTPLIB_H + +#if defined(__unix__) || defined(VMS) +#define GLOBALDEF +#define GLOBALREF extern +#elif defined(_WIN32) +#if defined BUILDING_LIBRARY +#define GLOBALDEF __declspec(dllexport) +#define GLOBALREF __declspec(dllexport) +#else +#define GLOBALREF __declspec(dllimport) +#endif +#endif + +#include +#include + +/* FtpAccess() type codes */ +#define FTPLIB_DIR 1 +#define FTPLIB_DIR_VERBOSE 2 +#define FTPLIB_FILE_READ 3 +#define FTPLIB_FILE_WRITE 4 + +/* FtpAccess() mode codes */ +#define FTPLIB_ASCII 'A' +#define FTPLIB_IMAGE 'I' +#define FTPLIB_TEXT FTPLIB_ASCII +#define FTPLIB_BINARY FTPLIB_IMAGE + +/* connection modes */ +#define FTPLIB_PASSIVE 1 +#define FTPLIB_PORT 2 + +/* connection option names */ +#define FTPLIB_CONNMODE 1 +#define FTPLIB_CALLBACK 2 +#define FTPLIB_IDLETIME 3 +#define FTPLIB_CALLBACKARG 4 +#define FTPLIB_CALLBACKBYTES 5 + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__UINT64_MAX) +typedef uint64_t fsz_t; +#else +typedef uint32_t fsz_t; +#endif + +typedef struct NetBuf netbuf; +typedef int (*FtpCallback)(netbuf *nControl, fsz_t xfered, void *arg); + +typedef struct FtpCallbackOptions { + FtpCallback cbFunc; /* function to call */ + void *cbArg; /* argument to pass to function */ + unsigned int bytesXferred; /* callback if this number of bytes transferred */ + unsigned int idleTime; /* callback if this many milliseconds have elapsed */ +} FtpCallbackOptions; + +GLOBALREF int ftplib_debug; +GLOBALREF void FtpInit(void); +GLOBALREF char *FtpLastResponse(netbuf *nControl); +GLOBALREF int FtpConnect(const char *host, netbuf **nControl); +GLOBALREF int FtpOptions(int opt, long val, netbuf *nControl); +GLOBALREF int FtpSetCallback(const FtpCallbackOptions *opt, netbuf *nControl); +GLOBALREF int FtpClearCallback(netbuf *nControl); +GLOBALREF int FtpLogin(const char *user, const char *pass, netbuf *nControl); +GLOBALREF int FtpAccess(const char *path, int typ, int mode, netbuf *nControl, + netbuf **nData); +GLOBALREF int FtpRead(void *buf, int max, netbuf *nData); +GLOBALREF int FtpWrite(const void *buf, int len, netbuf *nData); +GLOBALREF int FtpClose(netbuf *nData); +GLOBALREF int FtpSite(const char *cmd, netbuf *nControl); +GLOBALREF int FtpSysType(char *buf, int max, netbuf *nControl); +GLOBALREF int FtpMkdir(const char *path, netbuf *nControl); +GLOBALREF int FtpChdir(const char *path, netbuf *nControl); +GLOBALREF int FtpCDUp(netbuf *nControl); +GLOBALREF int FtpRmdir(const char *path, netbuf *nControl); +GLOBALREF int FtpPwd(char *path, int max, netbuf *nControl); +GLOBALREF int FtpNlst(const char *output, const char *path, netbuf *nControl); +GLOBALREF int FtpDir(const char *output, const char *path, netbuf *nControl); +GLOBALREF int FtpSize(const char *path, unsigned int *size, char mode, netbuf *nControl); +#if defined(__UINT64_MAX) +GLOBALREF int FtpSizeLong(const char *path, fsz_t *size, char mode, netbuf *nControl); +#endif +GLOBALREF int FtpModDate(const char *path, char *dt, int max, netbuf *nControl); +GLOBALREF int FtpGet(const char *output, const char *path, char mode, + netbuf *nControl); +GLOBALREF int FtpPut(const char *input, const char *path, char mode, + netbuf *nControl); +GLOBALREF int FtpRename(const char *src, const char *dst, netbuf *nControl); +GLOBALREF int FtpDelete(const char *fnm, netbuf *nControl); +GLOBALREF void FtpQuit(netbuf *nControl); + +#ifdef __cplusplus +}; +#endif + +#endif /* __FTPLIB_H */ diff --git a/EL/makefile b/EL/makefile new file mode 100644 index 0000000..ae04555 --- /dev/null +++ b/EL/makefile @@ -0,0 +1,16 @@ +all: lib_el.a lib_el.so libftp.a libftp.so + +lib_el.a:_el.h _el.cpp + g++ -c -o lib_el.a _el.cpp + +lib_el.so:_el.h _el.cpp + g++ -fPIC -shared -o lib_el.so _el.cpp + +libftp.a:ftplib.h ftplib.c + gcc -c -o libftp.a ftplib.c + +libftp.so:ftplib.h ftplib.c + gcc -fPIC -shared -o libftp.so ftplib.c + +clean: + rm -f lib_el.a lib_el.so libftp.a libftp.so diff --git a/README.md b/README.md new file mode 100644 index 0000000..94cccb4 --- /dev/null +++ b/README.md @@ -0,0 +1,638 @@ +# 网络编程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; +} +``` + diff --git a/client.cpp b/client.cpp new file mode 100644 index 0000000..86e4eaf --- /dev/null +++ b/client.cpp @@ -0,0 +1,29 @@ +// 测试同步通讯的客户端程序。 +#include "_el.h" +using namespace eviwbh; + +int main(int argc,char *argv[]) +{ + if (argc!=3) { cout << "Using: ./client ip port\n"; return -1;} + + ctcpclient tcpclient; + if (tcpclient.connect(argv[1],atoi(argv[2]))==false) + { + printf("tcpclient.connect failed.\n"); return -1; + } + + clogfile logfile; + logfile.open("/tmp/demo07.log"); + + string strsendbuffer,strrecvbuffer; + + for (int ii=1;ii<=100;ii++) + { + sformat(strsendbuffer,"这是第%d个。",ii); + logfile.write("%s\n",strsendbuffer.c_str()); + tcpclient.write(strsendbuffer); // 向服务端发送报文。 + + tcpclient.read(strrecvbuffer); // 等待服务端的回应。 + logfile.write("%s\n",strrecvbuffer.c_str()); + } +} \ No newline at end of file diff --git a/client_fork.cpp b/client_fork.cpp new file mode 100644 index 0000000..4d8ff1c --- /dev/null +++ b/client_fork.cpp @@ -0,0 +1,39 @@ +// 多进程版本的异步通讯的客户端程序。 +#include "_el.h" +using namespace eviwbh; + +int main(int argc,char *argv[]) +{ + if (argc!=3) { cout << "Using: ./client_fork ip port\n"; return -1;} + + ctcpclient tcpclient; + if (tcpclient.connect(argv[1],atoi(argv[2]))==false) + { + printf("tcpclient.connect failed.\n"); return -1; + } + + clogfile logfile; + logfile.open("/tmp/demo09.log"); + + if (fork()==0) + { + string strsendbuffer; + + for (int ii=1;ii<=1000000;ii++) + { + sformat(strsendbuffer,"这是第%d个。",ii); + logfile.write("%s\n",strsendbuffer.c_str()); + tcpclient.write(strsendbuffer); + } + } + else + { + string strrecvbuffer; + + for (int ii=1;ii<=1000000;ii++) + { + tcpclient.read(strrecvbuffer); + logfile.write("%s\n",strrecvbuffer.c_str()); + } + } +} \ No newline at end of file diff --git a/client_poll.cpp b/client_poll.cpp new file mode 100644 index 0000000..b12b487 --- /dev/null +++ b/client_poll.cpp @@ -0,0 +1,43 @@ +// I/O复用版本的异步通讯的客户端程序。 +#include "_el.h" +using namespace eviwbh; + +int main(int argc,char *argv[]) +{ + if (argc!=3) { cout << "Using: ./client_poll ip port\n"; return -1;} + + ctcpclient tcpclient; + if (tcpclient.connect(argv[1],atoi(argv[2]))==false) + { + printf("tcpclient.connect failed.\n"); return -1; + } + + clogfile logfile; + logfile.open("/tmp/demo10.log"); + + string strsendbuffer,strrecvbuffer; + + int ack=0; // 已接收回应报文的计数器。 + + for (int ii=1;ii<=1000000;ii++) + { + sformat(strsendbuffer,"这是第%d个。",ii); + logfile.write("%s\n",strsendbuffer.c_str()); + tcpclient.write(strsendbuffer); // 向服务端发送报文。 + + while (tcpclient.read(strrecvbuffer,-1)==true) // 检查tcp的缓冲区中是否有服务端回应报文。 + { + logfile.write("%s\n",strrecvbuffer.c_str()); + ack++; + } + } + + while (ack<1000000) + { + if (tcpclient.read(strrecvbuffer)==true) // 等待服务端回应报文。 + { + logfile.write("%s\n",strrecvbuffer.c_str()); + ack++; + } + } +} \ No newline at end of file diff --git a/server.cpp b/server.cpp new file mode 100644 index 0000000..49da726 --- /dev/null +++ b/server.cpp @@ -0,0 +1,25 @@ +// 测试同步/异步通讯的服务端程序。 +#include "_el.h" +using namespace eviwbh; + +int main(int argc,char *argv[]) +{ + if (argc!=2) { cout << "Using: ./server port\n"; return -1;} + + ctcpserver tcpserver; + if (tcpserver.initserver(atoi(argv[1]))==false) + { + printf("tcpserver.initserver() failed.\n"); return -1; + } + + tcpserver.accept(); + + string strsendbuffer,strrecvbuffer; + while (true) + { + if (tcpserver.read(strrecvbuffer)==false) break; // 接收客户端的报文。 + + sformat(strsendbuffer,"回复:%s",strrecvbuffer.c_str()); + if (tcpserver.write(strsendbuffer)==false) break; // 向客户发送回应报文。 + } +} diff --git a/tcpgetfiles.cpp b/tcpgetfiles.cpp new file mode 100644 index 0000000..38d846b --- /dev/null +++ b/tcpgetfiles.cpp @@ -0,0 +1,285 @@ +/* + * 程序名:tcpgetfiles.cpp,采用tcp协议,实现文件下载的客户端。 +*/ +#include "_el.h" +using namespace eviwbh; + +// 程序运行的参数结构体。 +struct st_arg +{ + int clienttype; // 客户端类型,1-上传文件;2-下载文件,本程序固定填2。 + char ip[31]; // 服务端的IP地址。 + int port; // 服务端的端口。 + char srvpath[256]; // 服务端文件存放的根目录。 + int ptype; // 文件下载成功后服务端文件的处理方式:1-删除文件;2-移动到备份目录。 + char srvpathbak[256]; // 文件成功下载后,服务端文件备份的根目录,当ptype==2时有效。 + bool andchild; // 是否下载srvpath目录下各级子目录的文件,true-是;false-否。 + char matchname[256]; // 待下载文件名的匹配规则,如"*.TXT,*.XML"。 + char clientpath[256]; // 客户端文件存放的根目录。 + int timetvl; // 扫描服务端目录文件的时间间隔,单位:秒。 + int timeout; // 进程心跳的超时时间。 + char pname[51]; // 进程名,建议用"tcpgetfiles_后缀"的方式。 +} starg; + +clogfile logfile; + +// 程序退出和信号2、15的处理函数。 +void EXIT(int sig); + +void _help(); + +// 把xml解析到参数starg结构中。 +bool _xmltoarg(const char *strxmlbuffer); + +ctcpclient tcpclient; + +bool login(const char *argv); // 登录业务。 + +string strrecvbuffer; // 发送报文的buffer。 +string strsendbuffer; // 接收报文的buffer。 + +// 文件下载的主函数。 +void _tcpgetfiles(); + +// 接收文件的内容。 +bool recvfile(const string &filename,const string &mtime,int filesize); + +cpactive pactive; // 进程心跳。 + +int main(int argc,char *argv[]) +{ + if (argc!=3) { _help(); return -1; } + + // 关闭全部的信号和输入输出。 + // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程。 + // 但请不要用 "kill -9 +进程号" 强行终止。 + //closeioandsignal(false); + signal(SIGINT,EXIT); signal(SIGTERM,EXIT); + + // 打开日志文件。 + if (logfile.open(argv[1])==false) + { + printf("打开日志文件失败(%s)。\n",argv[1]); return -1; + } + + // 解析xml,得到程序运行的参数。 + if (_xmltoarg(argv[2])==false) return -1; + + pactive.addpinfo(starg.timeout,starg.pname); // 把进程的心跳信息写入共享内存。 + + // 向服务端发起连接请求。 + if (tcpclient.connect(starg.ip,starg.port)==false) + { + logfile.write("tcpclient.connect(%s,%d) failed.\n",starg.ip,starg.port); EXIT(-1); + } + + // 登录业务。 + if (login(argv[2])==false) { logfile.write("login() failed.\n"); EXIT(-1); } + + // 调用文件下载的主函数。 + _tcpgetfiles(); + + EXIT(0); +} + + +// 登录业务。 +bool login(const char *argv) +{ + sformat(strsendbuffer,"%s2",argv); + // xxxxxxxxxxxxxx logfile.write("发送:%s\n",strsendbuffer.c_str()); + if (tcpclient.write(strsendbuffer)==false) return false; // 向服务端发送请求报文。 + + if (tcpclient.read(strrecvbuffer,10)==false) return false; // 接收服务端的回应报文。 + // xxxxxxxxxxxxxx logfile.write("接收:%s\n",strrecvbuffer.c_str()); + + logfile.write("登录(%s:%d)成功。\n",starg.ip,starg.port); + + return true; +} + +void EXIT(int sig) +{ + logfile.write("程序退出,sig=%d\n\n",sig); + + exit(0); +} + +void _help() +{ + printf("\n"); + printf("Using:/project/tools/bin/tcpgetfiles logfilename xmlbuffer\n\n"); + + printf("Sample:/project/tools/bin/procctl 20 /project/tools/bin/tcpgetfiles /log/idc/tcpgetfiles_surfdata.log " + "\"192.168.150.1285005"\ + "/tmp/client" + "1/tmp/server"\ + "true*" + "1050tcpgetfiles_surfdata\"\n"); + + printf("本程序是数据中心的公共功能模块,采用tcp协议从服务端下载文件。\n"); + printf("logfilename 本程序运行的日志文件。\n"); + printf("xmlbuffer 本程序运行的参数,如下:\n"); + printf("ip 服务端的IP地址。\n"); + printf("port 服务端的端口。\n"); + printf("ptype 文件下载成功后服务端文件的处理方式:1-删除文件;2-移动到备份目录。\n"); + printf("srvpath 服务端文件存放的根目录。\n"); + printf("srvpathbak 文件成功下载后,服务端文件备份的根目录,当ptype==2时有效。\n"); + printf("andchild 是否下载srvpath目录下各级子目录的文件,true-是;false-否,缺省为false。\n"); + printf("matchname 待下载文件名的匹配规则,如\"*.TXT,*.XML\"\n"); + printf("clientpath 客户端文件存放的根目录。\n"); + printf("timetvl 扫描服务目录文件的时间间隔,单位:秒,取值在1-30之间。\n"); + printf("timeout 本程序的超时时间,单位:秒,视文件大小和网络带宽而定,建议设置50以上。\n"); + printf("pname 进程名,尽可能采用易懂的、与其它进程不同的名称,方便故障排查。\n\n"); +} + +// 把xml解析到参数starg结构 +bool _xmltoarg(const 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)) { logfile.write("ptype not in (1,2).\n"); return false; } + + getxmlbuffer(strxmlbuffer,"srvpath",starg.srvpath); + if (strlen(starg.srvpath)==0) { logfile.write("srvpath is null.\n"); return false; } + + getxmlbuffer(strxmlbuffer,"srvpathbak",starg.srvpathbak); + if ((starg.ptype==2)&&(strlen(starg.srvpathbak)==0)) { logfile.write("srvpathbak 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,"clientpath",starg.clientpath); + if (strlen(starg.clientpath)==0) { logfile.write("clientpath is null.\n"); return false; } + + getxmlbuffer(strxmlbuffer,"timetvl",starg.timetvl); + if (starg.timetvl==0) { logfile.write("timetvl is null.\n"); return false; } + + // 扫描服务端目录文件的时间间隔(执行下载任务的时间间隔),单位:秒。 + // starg.timetvl没有必要超过30秒。 + if (starg.timetvl>30) starg.timetvl=30; + + // 进程心跳的超时时间,一定要大于starg.timetvl。 + getxmlbuffer(strxmlbuffer,"timeout",starg.timeout); + if (starg.timeout==0) { logfile.write("timeout is null.\n"); return false; } + if (starg.timeout<=starg.timetvl) { logfile.write("starg.timeout(%d) <= starg.timetvl(%d).\n",starg.timeout,starg.timetvl); return false; } + + getxmlbuffer(strxmlbuffer,"pname",starg.pname,50); + //if (strlen(starg.pname)==0) { logfile.write("pname is null.\n"); return false; } + + return true; +} + +// 文件下载的主函数。 +void _tcpgetfiles() +{ + while (true) + { + pactive.uptatime(); + + // 接收服务端的报文。 + if (tcpclient.read(strrecvbuffer,starg.timetvl+10)==false) + { + logfile.write("tcpclient.read() failed.\n"); return; + } + // logfile.write("strrecvbuffer=%s\n",strrecvbuffer.c_str()); + + // 处理心跳报文。 + if (strrecvbuffer=="ok") + { + strsendbuffer="ok"; + // xxxxxxxxxxx logfile.write("strsendbuffer=%s\n",strsendbuffer.c_str()); + if (tcpclient.write(strsendbuffer)==false) + { + logfile.write("tcpclient.write() failed.\n"); return; + } + } + + // 处理下载文件的请求报文。 + if (strrecvbuffer.find("") != string::npos) + { + // 解析下载文件请求报文的xml。 + string serverfilename; + string mtime; + int filesize=0; + getxmlbuffer(strrecvbuffer,"filename",serverfilename); + getxmlbuffer(strrecvbuffer,"mtime",mtime); + getxmlbuffer(strrecvbuffer,"size",filesize); + + // 客户端和服务端文件的目录是不一样的,以下代码生成客户端的文件名。 + // 把文件名中的srvpath替换成clientpath,要小心第三个参数 + string clientfilename; + clientfilename=serverfilename; + replacestr(clientfilename,starg.srvpath,starg.clientpath,false); + + // 接收文件的内容。 + logfile.write("recv %s(%d) ...",clientfilename.c_str(),filesize); + if (recvfile(clientfilename,mtime,filesize)==true) + { + logfile << "ok.\n"; + sformat(strsendbuffer,"%sok",serverfilename.c_str()); + } + else + { + logfile << "failed.\n"; + sformat(strsendbuffer,"%sfailed",serverfilename.c_str()); + } + + // 把接收结果返回给对端。 + // xxxxxxxxxxx logfile.write("strsendbuffer=%s\n",strsendbuffer.c_str()); + if (tcpclient.write(strsendbuffer)==false) + { + logfile.write("tcpclient.write() failed.\n"); return; + } + } + } +} + +// 接收文件的内容。 +bool recvfile(const string &filename,const string &mtime,int filesize) +{ + int totalbytes=0; // 已接收文件的总字节数。 + int onread=0; // 本次打算接收的字节数。 + char buffer[1000]; // 接收文件内容的缓冲区。 + cofile ofile; + + newdir(filename); + + if (ofile.open(filename,true,ios::out|ios::binary)==false) return false; + + while (true) + { + memset(buffer,0,sizeof(buffer)); + + // 计算本次应该接收的字节数。 + if (filesize-totalbytes>1000) onread=1000; + else onread=filesize-totalbytes; + + // 接收文件内容。 + if (tcpclient.read(buffer,onread)==false) return false; + + // 把接收到的内容写入文件。 + ofile.write(buffer,onread); + + // 计算已接收文件的总字节数,如果文件接收完,跳出循环。 + totalbytes=totalbytes+onread; + + if (totalbytes==filesize) break; + } + + ofile.closeandrename(); + + // 重置文件的时间。 + setmtime(filename,mtime); + + return true; +} \ No newline at end of file diff --git a/tcpputfiles.cpp b/tcpputfiles.cpp new file mode 100644 index 0000000..eb0cd3e --- /dev/null +++ b/tcpputfiles.cpp @@ -0,0 +1,353 @@ +/* + * 程序名:tcpputfiles.cpp,采用tcp协议,实现文件上传的客户端。 +*/ +#include "_el.h" +using namespace eviwbh; + +// 程序运行的参数结构体。 +struct st_arg +{ + int clienttype; // 客户端类型,1-上传文件;2-下载文件,本程序固定填1。 + char ip[31]; // 服务端的IP地址。 + int port; // 服务端的端口。 + char clientpath[256]; // 本地文件存放的根目录。 /data /data/aaa /data/bbb + int ptype; // 文件上传成功后本地文件的处理方式:1-删除文件;2-移动到备份目录。 + char clientpathbak[256]; // 文件成功上传后,本地文件备份的根目录,当ptype==2时有效。 + bool andchild; // 是否上传clientpath目录下各级子目录的文件,true-是;false-否。 + char matchname[256]; // 待上传文件名的匹配规则,如"*.TXT,*.XML"。 + char srvpath[256]; // 服务端文件存放的根目录。/data1 /data1/aaa /data1/bbb + int timetvl; // 扫描本地目录文件的时间间隔(执行文件上传任务的时间间隔),单位:秒。 + int timeout; // 进程心跳的超时时间。 + char pname[51]; // 进程名,建议用"tcpputfiles_后缀"的方式。 +} starg; + +// 帮助文档。 +void _help(); + +// 把xml解析到参数starg结构中。 +bool _xmltoarg(const char *strxmlbuffer); + +clogfile logfile; // 日志对象。 +ctcpclient tcpclient; // 创建tcp通讯的客户端对象。 + +// 程序退出和信号2、15的处理函数。 +void EXIT(int sig); + +bool activetest(); // 心跳。 + +string strsendbuffer; // 发送报文的buffer。 +string strrecvbuffer; // 接收报文的buffer。 + +// 向服务端发送登录报文,把客户端程序的参数传递给服务端。 +bool login(const char *argv); + +// 文件上传的主函数,执行一次文件上传的任务。 +bool _tcpputfiles(bool &bcontinue); + +// 处理传输文件的响应报文(删除或者转存本地的文件)。 +bool ackmessage(const string &strrecvbuffer); + +// 把文件的内容发送给对端。 +bool sendfile(const string &filename,const int filesize); + +cpactive pactive; // 进程心跳。 + +int main(int argc,char *argv[]) +{ + if (argc!=3) { _help(); return -1; } + + // 关闭全部的信号和输入输出。 + // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程。 + // 但请不要用 "kill -9 +进程号" 强行终止。 + // 在网络通讯程序中,一般不关IO,因为某些函数可能会往1和2中输出信息 + // 如果关了1和2,那么1和2会被socket重用,向1和2输出的信息会发送到网络中。 + //closeioandsignal(false); + signal(SIGINT,EXIT); signal(SIGTERM,EXIT); + + // 打开日志文件。 + if (logfile.open(argv[1])==false) + { + printf("打开日志文件失败(%s)。\n",argv[1]); return -1; + } + + // 解析xml,得到程序运行的参数。 + if (_xmltoarg(argv[2])==false) return -1; + + pactive.addpinfo(starg.timeout,starg.pname); // 把进程的心跳信息写入共享内存。 + + // 向服务端发起连接请求。 + if (tcpclient.connect(starg.ip,starg.port)==false) + { + logfile.write("tcpclient.connect(%s,%d) failed.\n",starg.ip,starg.port); EXIT(-1); + } + + // 向服务端发送登录报文,把客户端程序的参数传递给服务端。 + if (login(argv[2])==false) { logfile.write("login() failed.\n"); EXIT(-1); } + + bool bcontinue=true; // 如果调用_tcpputfiles()发送了文件,bcontinue为true,否则为false。 + + while (true) + { + // 调用文件上传的主函数,执行一次文件上传的任务。 + if (_tcpputfiles(bcontinue)==false) { logfile.write("_tcpputfiles() failed.\n"); EXIT(-1); } + + // 如果刚才执行文件上传任务的时候上传了文件,在上传的过程中,可能有新的文件陆续已生成, + // 那么,为保证文件被尽快上传,进程不体眠。(只有在刚才执行文件上传任务的时候没有上传文件的情况下才休眠) + if (bcontinue==false) + { + sleep(starg.timetvl); + + // 发送心跳报文。 + if (activetest()==false) break; + } + + pactive.uptatime(); + } + + EXIT(0); +} + +// 心跳。 +bool activetest() +{ + strsendbuffer="ok"; + // xxxxxxxxxx logfile.write("发送:%s\n",strsendbuffer.c_str()); + if (tcpclient.write(strsendbuffer)==false) return false; // 向服务端发送请求报文。 + + if (tcpclient.read(strrecvbuffer,10)==false) return false; // 接收服务端的回应报文。 + // xxxxxxxxxx logfile.write("接收:%s\n",strrecvbuffer.c_str()); + + // 心跳机制的代码可简单化处理,只需要收到对端的回应就行了,不必判断回应的内容。 + + return true; +} + +void EXIT(int sig) +{ + logfile.write("程序退出,sig=%d\n\n",sig); + + exit(0); +} + +// 程序的帮助文档。 +void _help() +{ + printf("\n"); + printf("Using:/project/tools/bin/tcpputfiles logfilename xmlbuffer\n\n"); + + printf("Sample:/project/tools/bin/procctl 20 /project/tools/bin/tcpputfiles /log/idc/tcpputfiles_surfdata.log "\ + "\"192.168.150.1285005"\ + "/tmp/client1" + "/tmp/server"\ + "true*.xml,*.txt,*.csv10"\ + "50tcpputfiles_surfdata\"\n\n"); + + printf("本程序是数据中心的公共功能模块,采用tcp协议把文件上传给服务端。\n"); + printf("logfilename 本程序运行的日志文件。\n"); + printf("xmlbuffer 本程序运行的参数,如下:\n"); + printf("ip 服务端的IP地址。\n"); + printf("port 服务端的端口。\n"); + printf("ptype 文件上传成功后的处理方式:1-删除文件;2-移动到备份目录。\n"); + printf("clientpath 本地文件存放的根目录。\n"); + printf("clientpathbak 文件成功上传后,本地文件备份的根目录,当ptype==2时有效。\n"); + printf("andchild 是否上传clientpath目录下各级子目录的文件,true-是;false-否,缺省为false。\n"); + printf("matchname 待上传文件名的匹配规则,如\"*.TXT,*.XML\"\n"); + printf("srvpath 服务端文件存放的根目录。\n"); + printf("timetvl 扫描本地目录文件的时间间隔,单位:秒,取值在1-30之间。\n"); + printf("timeout 本程序的超时时间,单位:秒,视文件大小和网络带宽而定,建议设置50以上。\n"); + printf("pname 进程名,尽可能采用易懂的、与其它进程不同的名称,方便故障排查。\n\n"); +} + +// 把xml解析到参数starg结构。 +bool _xmltoarg(const 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)) { logfile.write("ptype not in (1,2).\n"); return false; } + + getxmlbuffer(strxmlbuffer,"clientpath",starg.clientpath); + if (strlen(starg.clientpath)==0) { logfile.write("clientpath is null.\n"); return false; } + + getxmlbuffer(strxmlbuffer,"clientpathbak",starg.clientpathbak); + if ((starg.ptype==2)&&(strlen(starg.clientpathbak)==0)) { logfile.write("clientpathbak 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,"srvpath",starg.srvpath); + if (strlen(starg.srvpath)==0) { logfile.write("srvpath is null.\n"); return false; } + + getxmlbuffer(strxmlbuffer,"timetvl",starg.timetvl); + if (starg.timetvl==0) { logfile.write("timetvl is null.\n"); return false; } + + // 扫描本地目录文件的时间间隔(执行上传任务的时间间隔),单位:秒。 + // starg.timetvl没有必要超过30秒。 + if (starg.timetvl>30) starg.timetvl=30; + + // 进程心跳的超时时间,一定要大于starg.timetvl。 + getxmlbuffer(strxmlbuffer,"timeout",starg.timeout); + if (starg.timeout==0) { logfile.write("timeout is null.\n"); return false; } + if (starg.timeout<=starg.timetvl) { logfile.write("starg.timeout(%d) <= starg.timetvl(%d).\n",starg.timeout,starg.timetvl); return false; } + + getxmlbuffer(strxmlbuffer,"pname",starg.pname,50); + //if (strlen(starg.pname)==0) { logfile.write("pname is null.\n"); return false; } + + return true; +} + +// 向服务端发送登录报文,把客户端程序的参数传递给服务端。 +bool login(const char *argv) +{ + sformat(strsendbuffer,"%s1",argv); + // xxxxxxxxxx logfile.write("发送:%s\n",strsendbuffer.c_str()); + if (tcpclient.write(strsendbuffer)==false) return false; // 向服务端发送请求报文。 + + if (tcpclient.read(strrecvbuffer,10)==false) return false; // 接收服务端的回应报文。 + // xxxxxxxxxx logfile.write("接收:%s\n",strrecvbuffer.c_str()); + + logfile.write("登录(%s:%d)成功。\n",starg.ip,starg.port); + + return true; +} + +// 文件上传的主函数,执行一次文件上传的任务。 +bool _tcpputfiles(bool &bcontinue) +{ + bcontinue=false; + + cdir dir; + + // 打开starg.clientpath目录。 + if (dir.opendir(starg.clientpath,starg.matchname,10000,starg.andchild)==false) + { + logfile.write("dir.opendir(%s) 失败。\n",starg.clientpath); return false; + } + + int delayed=0; // 未收到对端确认报文的文件数量,发送了一个文件就加1,接收到了一个回应就减1。 + + // 遍历目录中的每个文件 + while (dir.readdir()) + { + bcontinue=true; + + // 把文件名、修改时间、文件大小组成报文,发送给对端。 + sformat(strsendbuffer,"%s%s%d", + dir.m_ffilename.c_str(),dir.m_mtime.c_str(),dir.m_filesize); + + // xxxxxxxxxxxxxxx logfile.write("strsendbuffer=%s\n",strsendbuffer.c_str()); + if (tcpclient.write(strsendbuffer)==false) + { + logfile.write("tcpclient.write() failed.\n"); return false; + } + + // 发送文件内容。 + logfile.write("send %s(%d) ...",dir.m_ffilename.c_str(),dir.m_filesize); + if (sendfile(dir.m_ffilename,dir.m_filesize)==true) + { + logfile << "ok.\n"; delayed++; + } + else + { + logfile << "failed.\n"; tcpclient.close(); return false; + } + + pactive.uptatime(); + + // 接收服务端的确认报文。 + while (delayed>0) + { + if (tcpclient.read(strrecvbuffer,-1)==false) break; + // xxxxxxxxxxxxxxx logfile.write("strrecvbuffer=%s\n",strrecvbuffer.c_str()); + + // 处理服务端的确认报文(删除本地文件或把本地文件移动到备份目录)。 + ackmessage(strrecvbuffer); + } + } + + // 继续接收对端的确认报文。 + while (delayed>0) + { + if (tcpclient.read(strrecvbuffer,10)==false) break; + // xxxxxxxxxxxxxxx logfile.write("strrecvbuffer=%s\n",strrecvbuffer.c_str()); + + // 处理传输文件的响应报文(删除或者转存本地的文件)。 + delayed--; + ackmessage(strrecvbuffer); + } + + return true; +} + +// 把文件的内容发送给对端。 +bool sendfile(const string &filename,const int filesize) +{ + int onread=0; // 每次打算从文件中读取的字节数。 + char buffer[1000]; // 存放读取数据的buffer,buffer的大小可参考硬盘一次读取数据量(4K为宜)。 + int totalbytes=0; // 从文件中已读取的字节总数。 + cifile ifile; // 读取文件的对象。 + + // 必须以二进制的方式操作文件。 + if (ifile.open(filename,ios::in|ios::binary)==false) return false; + + while (true) + { + memset(buffer,0,sizeof(buffer)); + + // 计算本次应该读取的字节数,如果剩余的数据超过1000字节,就读1000字节。 + if (filesize-totalbytes>1000) onread=1000; + else onread=filesize-totalbytes; + + // 从文件中读取数据。 + ifile.read(buffer,onread); + + // 把读取到的数据发送给对端。 + if (tcpclient.write(buffer,onread)==false) { return false; } + + // 计算文件已读取的字节总数,如果文件已读完,跳出循环。 + totalbytes=totalbytes+onread; + + if (totalbytes==filesize) break; + } + + return true; +} + +// 处理传输文件的响应报文(删除或者转存本地的文件)。 +bool ackmessage(const string &strrecvbuffer) +{ + // /tmp/client/2.xmlok + string filename; // 本地文件名。 + string result; // 对端接收文件的结果。 + getxmlbuffer(strrecvbuffer,"filename",filename); + getxmlbuffer(strrecvbuffer,"result",result); + + // 如果服务端接收文件不成功,直接返回(下次执行文件传输任务时将会重传)。 + if (result!="ok") return true; + + // 如果starg.ptype==1,删除文件。 + if (starg.ptype==1) + { + if (remove(filename.c_str())!=0) { logfile.write("remove(%s) failed.\n",filename.c_str()); return false; } + } + + // 如果starg.ptype==2,移动到备份目录。 + if (starg.ptype==2) + { + // 生成转存后的备份目录文件名。 例如:/tmp/client/2.xml /tmp/clientbak/2.xml + string bakfilename=filename; + replacestr(bakfilename,starg.clientpath,starg.clientpathbak,false); // 注意,第三个参数一定要填false。 + if (renamefile(filename,bakfilename)==false) + { logfile.write("renamefile(%s,%s) failed.\n",filename.c_str(),bakfilename.c_str()); return false; } + } + + return true; +} \ No newline at end of file