开发工具-公共方法

92 阅读41分钟

代码汇总

这是文章中所有的开发工具的代码汇总,代码大部分来自于吴从周先生的教程,以及在学习过程中练习的代码。
在工作中完全可以借鉴与应用,感谢吴先生这位技术大牛提供非常实用的代码

声明

头文件
/****************************************************************************************/
/*   程序名:_public.h,此程序是开发框架公用函数和类的声明文件。                                                                               */
/****************************************************************************************/

#ifndef __PUBLIC_HH
#define __PUBLIC_HH 1

#include "_cmpublic.h"

///////////////////////////////////// /////////////////////////////////////
// 以下是字符串操作相关的函数和类

// 安全的strcpy函数。
// dest:目标字符串,不需要初始化,在STRCPY函数中会对它进行初始化。
// destlen:目标字符串dest占用内存的大小。
// src:原字符串。
// 返回值:目标字符串dest的地址。
// 注意,超出dest容量的内容将丢弃。
char *STRCPY(char* dest,const size_t destlen,const char* src);

// 安全的strncpy函数。
// dest:目标字符串,不需要初始化,在STRNCPY函数中会对它进行初始化。
// destlen:目标字符串dest占用内存的大小。
// src:原字符串。
// n:待复制的字节数。
// 返回值:目标字符串dest的地址。
// 注意,超出dest容量的内容将丢弃。
char *STRNCPY(char* dest,const size_t destlen,const char* src,size_t n);

// 安全的strcat函数。
// dest:目标字符串。
// destlen:目标字符串dest占用内存的大小。
// src:待追加的字符串。
// 返回值:目标字符串dest的地址。
// 注意,超出dest容量的内容将丢弃。
char *STRCAT(char* dest,const size_t destlen,const char* src);

// 安全的strncat函数。
// dest:目标字符串。
// destlen:目标字符串dest占用内存的大小。
// src:待追加的字符串。
// n:待追加的字节数。
// 返回值:目标字符串dest的地址。
// 注意,超出dest容量的内容将丢弃。
char *STRNCAT(char* dest,const size_t destlen,const char* src,size_t n);

// 安全的sprintf函数。
// 将可变参数(...)按照fmt描述的格式输出到dest字符串中。
// dest:输出字符串,不需要初始化,在SPRINTF函数中会对它进行初始化。
// destlen:输出字符串dest占用内存的大小,如果格式化后的字符串长度大于destlen-1,后面的内容将丢弃。
// fmt:格式控制描述。
// ...:填充到格式控制描述fmt中的参数。
// 返回值:格式化后的字符串的长度,程序员一般不关心返回值。
int SPRINTF(char *dest,const size_t destlen,const char *fmt,...);

// 安全的snprintf函数。
// 将可变参数(...)按照fmt描述的格式输出到dest字符串中。
// dest:输出字符串,不需要初始化,在SNPRINTF函数中会对它进行初始化。
// destlen:输出字符串dest占用内存的大小,如果格式化后的字符串长度大于destlen-1,后面的内容将丢弃。
// n:把格式化后的字符串截取n-1存放到dest中,如果n>destlen-1,则取destlen-1。
// fmt:格式控制描述。
// ...:填充到格式控制描述fmt中的参数。
// 返回值:格式化后的字符串的长度,程序员一般不关心返回值。
// 注意:windows和linux平台下的snprintf函数的第三个参数n的用法略有不同。假设格式化后的字符串的长度
// 超过10,第三个参数n取值是10,那么,在windows平台下dest的长度将是10,linux平台下dest的长度却是9。
int SNPRINTF(char *dest,const size_t destlen,size_t n,const char *fmt,...);

// 删除字符串左边指定的字符。
// str:待处理的字符串。
// chr:需要删除的字符。
void DeleteLChar(char *str,const char chr);

// 删除字符串右边指定的字符。
// str:待处理的字符串。
// chr:需要删除的字符。
void DeleteRChar(char *str,const char chr);

// 删除字符串左右两边指定的字符。
// str:待处理的字符串。
// chr:需要删除的字符。
void DeleteLRChar(char *str,const char chr);

// 把字符串中的小写字母转换成大写,忽略不是字母的字符。
// str:待转换的字符串,支持char[]和string两种类型。
void ToUpper(char *str);
void ToUpper(string &str);

// 把字符串中的大写字母转换成小写,忽略不是字母的字符。
// str:待转换的字符串,支持char[]和string两种类型。
void ToLower(char *str);
void ToLower(string &str);

// 字符串替换函数。
// 在字符串str中,如果存在字符串str1,就替换为字符串str2。
// str:待处理的字符串。
// str1:旧的内容。
// str2:新的内容。
// bloop:是否循环执行替换。
// 注意:
// 1、如果str2比str1要长,替换后str会变长,所以必须保证str有足够的空间,否则内存会溢出。
// 2、如果str2中包含了str1的内容,且bloop为true,这种做法存在逻辑错误,UpdateStr将什么也不做。
void UpdateStr(char *str,const char *str1,const char *str2,const bool bloop=true);

// 从一个字符串中提取出数字、符号和小数点,存放到另一个字符串中。
// src:原字符串。
// dest:目标字符串。
// bsigned:是否包括符号(+和-),true-包括;false-不包括。
// bdot:是否包括小数点的圆点符号,true-包括;false-不包括。
void PickNumber(const char *src,char *dest,const bool bsigned,const bool bdot);

// 正则表达式,判断一个字符串是否匹配另一个字符串。
// 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
{
public:
  vector<string> m_vCmdStr;  // 存放拆分后的字段内容。

  CCmdStr();  // 构造函数。
  CCmdStr(const string &buffer,const char *sepstr,const bool bdelspace=false);

  // 把字符串拆分到m_vCmdStr容器中。
  // buffer:待拆分的字符串。
  // sepstr:buffer中采用的分隔符,注意,sepstr参数的数据类型不是字符,是字符串,如","、" "、"|"、"~!~"。
  // bdelspace:拆分后是否删除字段内容前后的空格,true-删除;false-不删除,缺省不删除。
  void SplitToCmd(const string &buffer,const char *sepstr,const bool bdelspace=false);

  // 获取拆分后字段的个数,即m_vCmdStr容器的大小。
  int CmdCount();

  // 从m_vCmdStr容器获取字段内容。
  // inum:字段的顺序号,类似数组的下标,从0开始。
  // value:传入变量的地址,用于存放字段内容。
  // 返回值:true-成功;如果inum的取值超出了m_vCmdStr容器的大小,返回失败。
  bool GetValue(const int inum,char *value,const int ilen=0); // 字符串,ilen缺省值为0。
  bool GetValue(const int inum,int  *value); // int整数。
  bool GetValue(const int inum,unsigned int *value); // unsigned int整数。
  bool GetValue(const int inum,long *value); // long整数。
  bool GetValue(const int inum,unsigned long *value); // unsigned long整数。
  bool GetValue(const int inum,double *value); // 双精度double。
  bool GetValue(const int inum,bool *value); // bool型。

  ~CCmdStr(); // 析构函数。
};
///////////////////////////////////// /////////////////////////////////////


///////////////////////////////////// /////////////////////////////////////
// 解析xml格式字符串的函数族。
// xml格式的字符串的内容如下:
// <filename>/tmp/_public.h</filename><mtime>2020-01-01 12:20:35</mtime><size>18348</size>
// <filename>/tmp/_public.cpp</filename><mtime>2020-01-01 10:10:15</mtime><size>50945</size>
// xmlbuffer:待解析的xml格式字符串。
// fieldname:字段的标签名。
// value:传入变量的地址,用于存放字段内容,支持bool、int、insigned int、long、
//       unsigned long、double和char[]。
// 注意,当value参数的数据类型为char []时,必须保证value数组的内存足够,否则可能发生
//       内存溢出的问题,也可以用ilen参数限定获取字段内容的长度,ilen的缺省值为0,表示不限长度。
// 返回值:true-成功;如果fieldname参数指定的标签名不存在,返回失败。
bool GetXMLBuffer(const char *xmlbuffer,const char *fieldname,char *value,const int ilen=0);
bool GetXMLBuffer(const char *xmlbuffer,const char *fieldname,bool *value);
bool GetXMLBuffer(const char *xmlbuffer,const char *fieldname,int  *value);
bool GetXMLBuffer(const char *xmlbuffer,const char *fieldname,unsigned int *value);
bool GetXMLBuffer(const char *xmlbuffer,const char *fieldname,long *value);
bool GetXMLBuffer(const char *xmlbuffer,const char *fieldname,unsigned long *value);
bool GetXMLBuffer(const char *xmlbuffer,const char *fieldname,double *value);
///////////////////////////////////// /////////////////////////////////////

///////////////////////////////////// /////////////////////////////////////
/*
  取操作系统的时间。
  stime:用于存放获取到的时间字符串。
  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与上述格式都匹配,stime的内容将为空。
    4)时间的年份是四位,其它的可能是一位和两位,如果不足两位,在前面补0。
*/
void LocalTime(char *stime,const char *fmt=0,const int timetvl=0);

// 把整数表示的时间转换为字符串表示的时间。
// ltime:整数表示的时间。
// stime:字符串表示的时间。
// fmt:输出字符串时间stime的格式,与LocalTime函数的fmt参数相同,如果fmt的格式不正确,stime将为空。
void timetostr(const time_t ltime,char *stime,const char *fmt=0);

// 把字符串表示的时间转换为整数表示的时间。
// stime:字符串表示的时间,格式不限,但一定要包括yyyymmddhh24miss,一个都不能少,顺序也不能变。
// 返回值:整数表示的时间,如果stime的格式不正确,返回-1。
time_t strtotime(const char *stime);

// 把字符串表示的时间加上一个偏移的秒数后得到一个新的字符串表示的时间。
// in_stime:输入的字符串格式的时间,格式不限,但一定要包括yyyymmddhh24miss,一个都不能少,顺序也不能变。
// out_stime:输出的字符串格式的时间。
// timetvl:需要偏移的秒数,正数往后偏移,负数往前偏移。
// fmt:输出字符串时间out_stime的格式,与LocalTime函数的fmt参数相同。
// 注意:in_stime和out_stime参数可以是同一个变量的地址,如果调用失败,out_stime的内容会清空。
// 返回值:true-成功,false-失败,如果返回失败,可以认为是in_stime的格式不正确。
bool AddTime(const char *in_stime,char *out_stime,const int timetvl,const char *fmt=0);
///////////////////////////////////// /////////////////////////////////////

///////////////////////////////////// /////////////////////////////////////
// 这是一个精确到微秒的计时器。
class CTimer
{
private:
public:
  struct timeval m_start;   // 开始计时的时间。
  struct timeval m_end;     // 计时完成的时间。

  // 开始计时。
  void Start();
  CTimer();  // 构造函数中会调用Start方法。

  // 计算已逝去的时间,单位:秒,小数点后面是微秒。
  // 每调用一次本方法之后,自动调用Start方法重新开始计时。
  double Elapsed();
};
///////////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////// /////////////////////////////////////
// 目录操作相关的类

// 根据绝对路径的文件名或目录名逐级的创建目录。
// pathorfilename:绝对路径的文件名或目录名。
// bisfilename:说明pathorfilename的类型,true-pathorfilename是文件名,否则是目录名,缺省值为true。
// 返回值:true-成功,false-失败,如果返回失败,原因有大概有三种情况:1)权限不足;2)pathorfilename参数不是合法的文件名或目录名;3)磁盘空间不足。
bool MKDIR(const char *pathorfilename,bool bisfilename=true);


// 获取某目录及其子目录中的文件列表信息。
class CDir
{
public:
  char m_DirName[301];        // 目录名,例如:/tmp/root。
  char m_FileName[301];       // 文件名,不包括目录名,例如:data.xml。
  char m_FullFileName[301];   // 文件全名,包括目录名,例如:/tmp/root/data.xml。
  int  m_FileSize;            // 文件的大小,单位:字节。
  char m_ModifyTime[21];      // 文件最后一次被修改的时间,即stat结构体的st_mtime成员。
  char m_CreateTime[21];      // 文件生成的时间,即stat结构体的st_ctime成员。
  char m_AccessTime[21];      // 文件最后一次被访问的时间,即stat结构体的st_atime成员。
  char m_DateFMT[25];         // 文件时间显示格式,由SetDateFMT方法设置。

  vector<string> m_vFileName; // 存放OpenDir方法获取到的绝对路径文件名清单。
  int m_pos;                  // 已读取m_vFileName容器的位置,每调用一次ReadDir方法m_pos加1CDir();  // 构造函数。

  void initdata(); // 初始化成员变量。

  // 设置文件时间的格式,支持"yyyy-mm-dd hh24:mi:ss"和"yyyymmddhh24miss"两种,缺省是前者。
  void SetDateFMT(const char *in_DateFMT);

  // 打开目录,获取目录中的文件列表信息,存放于m_vFileName容器中。
  // in_DirName,待打开的目录名,采用绝对路径,如/tmp/root。
  // in_MatchStr,待获取文件名的匹配规则,不匹配的文件被忽略,具体请参见开发框架的MatchStr函数。
  // in_MaxCount,获取文件的最大数量,缺省值为10000个。
  // bAndChild,是否打开各级子目录,缺省值为false-不打开子目录。
  // bSort,是否对获取到的文件列表(即m_vFileName容器中的内容)进行排序,缺省值为false-不排序。
  // 返回值:true-成功,false-失败,如果in_DirName参数指定的目录不存在,OpenDir方法会创建该目录,如果创建失败,返回false,如果当前用户对in_DirName目录下的子目录没有读取权限也会返回false。
  bool OpenDir(const char *in_DirName,const char *in_MatchStr,const unsigned int in_MaxCount=10000,const bool bAndChild=false,bool bSort=false);

  // 这是一个递归函数,被OpenDir()的调用,在CDir类的外部不需要调用它。
  bool _OpenDir(const char *in_DirName,const char *in_MatchStr,const unsigned int in_MaxCount,const bool bAndChild);

  // 从m_vFileName容器中获取一条记录(文件名),同时获取该文件的大小、修改时间等信息。
  // 调用OpenDir方法时,m_vFileName容器被清空,m_pos归零,每调用一次ReadDir方法m_pos加1。
  // 当m_pos小于m_vFileName.size(),返回true,否则返回false。
  bool ReadDir();

  ~CDir();  // 析构函数。
};

///////////////////////////////////// /////////////////////////////////////

///////////////////////////////////// /////////////////////////////////////
// 文件操作相关的函数和类

// 删除文件,类似Linux系统的rm命令。
// filename:待删除的文件名,建议采用绝对路径的文件名,例如/tmp/root/data.xml。
// times:执行删除文件的次数,缺省是1,建议不要超过3,从实际应用的经验看来,如果删除文件第1次不成功,再尝试2次是可以的,更多次就意义不大了。还有,如果执行删除失败,usleep(100000)后再重试。
// 返回值:true-成功;false-失败,失败的主要原因是权限不足。
// 在应用开发中,可以用REMOVE函数代替remove库函数。
bool REMOVE(const char *filename,const int times=1);

// 重命名文件,类似Linux系统的mv命令。
// srcfilename:原文件名,建议采用绝对路径的文件名。
// dstfilename:目标文件名,建议采用绝对路径的文件名。
// times:执行重命名文件的次数,缺省是1,建议不要超过3,从实际应用的经验看来,如果重命名文件第1次不成功,再尝试2次是可以的,更多次就意义不大了。还有,如果执行重命名失败,usleep(100000)后再重试。
// 返回值:true-成功;false-失败,失败的主要原因是权限不足或磁盘空间不够,如果原文件和目标文件不在同一个磁盘分区,重命名也可能失败。
// 注意,在重命名文件之前,会自动创建dstfilename参数中包含的目录。
// 在应用开发中,可以用RENAME函数代替rename库函数。
bool RENAME(const char *srcfilename,const char *dstfilename,const int times=1);

// 复制文件,类似Linux系统的cp命令。
// srcfilename:原文件名,建议采用绝对路径的文件名。
// dstfilename:目标文件名,建议采用绝对路径的文件名。
// 返回值:true-成功;false-失败,失败的主要原因是权限不足或磁盘空间不够。
// 注意:
// 1)在复制文件之前,会自动创建dstfilename参数中的目录名。
// 2)复制文件的过程中,采用临时文件命名的方法,复制完成后再改名为dstfilename,避免中间状态的文件被读取。
// 3)复制后的文件的时间与原文件相同,这一点与Linux系统cp命令不同。
bool COPY(const char *srcfilename,const char *dstfilename);

// 获取文件的大小。
// filename:待获取的文件名,建议采用绝对路径的文件名。
// 返回值:如果文件不存在或没有访问权限,返回-1,成功返回文件的大小,单位是字节。
int FileSize(const char *filename);

// 获取文件的时间。
// filename:待获取的文件名,建议采用绝对路径的文件名。
// mtime:用于存放文件的时间,即stat结构体的st_mtime。
// fmt:设置时间的输出格式,与LocalTime函数相同,但缺省是"yyyymmddhh24miss"。
// 返回值:如果文件不存在或没有访问权限,返回false,成功返回true。
bool FileMTime(const char *filename,char *mtime,const char *fmt=0);

// 重置文件的修改时间属性。
// filename:待重置的文件名,建议采用绝对路径的文件名。
// stime:字符串表示的时间,格式不限,但一定要包括yyyymmddhh24miss,一个都不能少,顺序也不能变。
// 返回值:true-成功;false-失败,失败的原因保存在errno中。
bool UTime(const char *filename,const char *mtime);

// 打开文件。
// FOPEN函数调用fopen库函数打开文件,如果文件名中包含的目录不存在,就创建目录。
// FOPEN函数的参数和返回值与fopen函数完全相同。
// 在应用开发中,用FOPEN函数代替fopen库函数。
FILE *FOPEN(const char *filename,const char *mode);

// 从文本文件中读取一行。
// fp:已打开的文件指针。
// buffer:用于存放读取的内容,buffer必须大于readsize+1,否则可能会造成读到的数据不完整或内存的溢出。
// readsize:本次打算读取的字节数,如果已经读取到了行结束标志,函数返回。
// endbz:行内容结束的标志,缺省为空,表示行内容以"\n"为结束标志。
// 返回值:true-成功;false-失败,一般情况下,失败可以认为是文件已结束。
bool FGETS(const FILE *fp,char *buffer,const int readsize,const char *endbz=0);

// 文件操作类声明
class CFile
{
private:
  FILE *m_fp;        // 文件指针
  bool  m_bEnBuffer; // 是否启用缓冲,true-启用;false-不启用,缺省是启用。
  char  m_filename[301]; // 文件名,建议采用绝对路径的文件名。
  char  m_filenametmp[301]; // 临时文件名,在m_filename后加".tmp"。

public:
  CFile();   // 构造函数。

  bool IsOpened();  // 判断文件是否已打开,返回值:true-已打开;false-未打开。

  // 打开文件。
  // filename:待打开的文件名,建议采用绝对路径的文件名。
  // openmode:打开文件的模式,与fopen库函数的打开模式相同。
  // bEnBuffer:是否启用缓冲,true-启用;false-不启用,缺省是启用。
  // 注意:如果待打开的文件的目录不存在,就会创建目录。
  bool Open(const char *filename,const char *openmode,bool bEnBuffer=true);

  // 关闭文件指针,并删除文件。
  bool CloseAndRemove();

  // 专为重命名而打开文件,参数与Open方法相同。
  // 注意:OpenForRename打开的是filename后加".tmp"的临时文件,所以openmode只能是"a"、"a+"、"w"、"w+"。
  bool OpenForRename(const char *filename,const char *openmode,bool bEnBuffer=true);
  // 关闭文件指针,并把OpenForRename方法打开的临时文件名重命名为filename。
  bool CloseAndRename();

  // 调用fprintf向文件写入数据,参数与fprintf库函数相同,但不需要传入文件指针。
  void Fprintf(const char *fmt,...);

  // 从文件中读取以换行符"\n"结束的一行,类似fgets函数。
  // buffer:用于存放读取的内容,buffer必须大于readsize+1,否则可能会造成内存的溢出。
  // readsize:本次打算读取的字节数,如果已经读取到了结束标志"\n",函数返回。
  // bdelcrt:是否删除行结束标志"\r"和"\n",true-删除;false-不删除,缺省值是false。
  // 返回值:true-成功;false-失败,一般情况下,失败可以认为是文件已结束。
  bool Fgets(char *buffer,const int readsize,bool bdelcrt=false);

  // 从文件文件中读取一行。
  // buffer:用于存放读取的内容,buffer必须大于readsize+1,否则可能会造成读到的数据不完整或内存的溢出。
  // readsize:本次打算读取的字节数,如果已经读取到了结束标志,函数返回。
  // endbz:行内容结束的标志,缺省为空,表示行内容以"\n"为结束标志。
  // 返回值:true-成功;false-失败,一般情况下,失败可以认为是文件已结束。
  bool FFGETS(char *buffer,const int readsize,const char *endbz=0);

  // 从文件中读取数据块。
  // ptr:用于存放读取的内容。
  // size:本次打算读取的字节数。
  // 返回值:本次从文件中成功读取的字节数,如果文件未结束,返回值等于size,如果文件已结束,返回值为实际读取的字节数。
  size_t Fread(void *ptr,size_t size);

  // 向文件中写入数据块。
  // ptr:待写入数据块的地址。
  // size:待写入数据块的字节数。
  // 返回值:本次成功写入的字节数,如果磁盘空间足够,返回值等于size。
  size_t Fwrite(const void *ptr,size_t size);

  // 关闭文件指针,如果存在临时文件,就删除它。
  void Close();

 ~CFile();   // 析构函数会调用Close方法。
};

///////////////////////////////////// /////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////
// 以下是日志文件操作类

// 日志文件操作类
class CLogFile
{
public:
  FILE   *m_tracefp;           // 日志文件指针。
  char    m_filename[301];     // 日志文件名,建议采用绝对路径。
  char    m_openmode[11];      // 日志文件的打开方式,一般采用"a+"。
  bool    m_bEnBuffer;         // 写入日志时,是否启用操作系统的缓冲机制,缺省不启用。
  bool    m_bBackup;           // 是否自动切换,日志文件大小超过m_MaxLogSize将自动切换,缺省启用。
  long    m_MaxLogSize;        // 最大日志文件的大小,单位M,缺省100M。
  // pthread_spinlock_t spin;  // 初学暂时不要关心这行代码。

  // 构造函数。
  // MaxLogSize:最大日志文件的大小,单位M,缺省100M,最小为10M。
  CLogFile(const long MaxLogSize=100);  

  // 打开日志文件。
  // filename:日志文件名,建议采用绝对路径,如果文件名中的目录不存在,就先创建目录。
  // openmode:日志文件的打开方式,与fopen库函数打开文件的方式相同,缺省值是"a+"。
  // bBackup:是否自动切换,true-切换,false-不切换,在多进程的服务程序中,如果多个进程共用一个日志文件,bBackup必须为false。
  // bEnBuffer:是否启用文件缓冲机制,true-启用,false-不启用,如果启用缓冲区,那么写进日志文件中的内容不会立即写入文件,缺省是不启用。
  bool Open(const char *filename,const char *openmode=0,bool bBackup=true,bool bEnBuffer=false);

  // 如果日志文件大于m_MaxLogSize的值,就把当前的日志文件名改为历史日志文件名,再创建新的当前日志文件。
  // 备份后的文件会在日志文件名后加上日期时间,如/tmp/log/filetodb.log.20200101123025。
  // 注意,在多进程的程序中,日志文件不可切换,多线的程序中,日志文件可以切换。
  bool BackupLogFile();

  // 把内容写入日志文件,fmt是可变参数,使用方法与printf库函数相同。
  // Write方法会写入当前的时间,WriteEx方法不写时间。
  bool Write(const char *fmt,...);
  bool WriteEx(const char *fmt,...);

  // 关闭日志文件
  void Close();

  ~CLogFile();  // 析构函数会调用Close方法。
};
///////////////////////////////////////////////////////////////////////////////////////////////////

// 参数文件操作类。
// CIniFile类操作的不是传统的ini文件,是xml格式的参数文件,例如:
/*
 * 文件名:hssms.xml
<?xml version="1.0" encoding="gbk" ?>
<root>
    <!-- 程序运行的日志文件名。 -->
    <logpath>/log/hssms</logpath>

    <!-- 数据库连接参数。 -->
    <connstr>hssms/smspwd@hssmszx</connstr>

    <!-- 数据文件存放的根目录。 -->
    <datapath>/data/hssms</datapath>

    <!-- 中心服务器的ip地址。 -->
    <serverip>192.168.1.1</serverip>

    <!-- 中心服务器的通讯端口。 -->
    <port>5058</port>

    <!-- 是否采用长连接,true-是;false-否。 -->
    <online>true</online>
</root>
*/

class CIniFile
{
public:
  string m_xmlbuffer; // 存放参数文件全部的内容,由LoadFile方法载入。

  CIniFile();

  // 把参数文件的内容载入到m_xmlbuffer成员变量中。
  bool LoadFile(const char *filename);
 
  // 获取参数的值。
  // fieldname:字段的标签名。
  // value:传入变量的地址,用于存放字段的值,支持bool、int、insigned int、long、unsigned long、double和char[]。
  // 注意,当value参数的数据类型为char []时,必须保证value的内存足够,否则可能发生内存溢出的问题,
  // 也可以用ilen参数限定获取字段内容的长度,ilen的缺省值为0,表示不限定获取字段内容的长度。
  // 返回值:true-成功;false-失败。
  bool GetValue(const char *fieldname,bool *value);
  bool GetValue(const char *fieldname,int  *value);
  bool GetValue(const char *fieldname,unsigned int *value);
  bool GetValue(const char *fieldname,long *value);
  bool GetValue(const char *fieldname,unsigned long *value);
  bool GetValue(const char *fieldname,double *value);
  bool GetValue(const char *fieldname,char *value,const int ilen=0);
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// 以下是socket通讯的函数和类

// socket通讯的客户端类
class CTcpClient
{
public:
  int  m_connfd;    // 客户端的socket.
  char m_ip[21];    // 服务端的ip地址。
  int  m_port;      // 与服务端通讯的端口。
  bool m_btimeout;  // 调用Read方法时,失败的原因是否是超时:true-超时,false-未超时。
  int  m_buflen;    // 调用Read方法后,接收到的报文的大小,单位:字节。

  CTcpClient();  // 构造函数。

  // 向服务端发起连接请求。
  // ip:服务端的ip地址。
  // port:服务端监听的端口。
  // 返回值:true-成功;false-失败。
  bool ConnectToServer(const char *ip,const int port);

  // 接收服务端发送过来的数据。
  // buffer:接收数据缓冲区的地址,数据的长度存放在m_buflen成员变量中。
  // itimeout:等待数据的超时时间,单位:秒,缺省值是0-无限等待。
  // 返回值:true-成功;false-失败,失败有两种情况:1)等待超时,成员变量m_btimeout的值被设置为true;2)socket连接已不可用。
  bool Read(char *buffer,const int itimeout=0);

  // 向服务端发送数据。
  // buffer:待发送数据缓冲区的地址。
  // ibuflen:待发送数据的大小,单位:字节,缺省值为0,如果发送的是ascii字符串,ibuflen取0,如果是二进制流数据,ibuflen为二进制数据块的大小。
  // 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
  bool Write(const char *buffer,const int ibuflen=0);

  // 断开与服务端的连接
  void Close();

  ~CTcpClient();  // 析构函数自动关闭socket,释放资源。
};

// socket通讯的服务端类
class CTcpServer
{
private:
  int m_socklen;                    // 结构体struct sockaddr_in的大小。
  struct sockaddr_in m_clientaddr;  // 客户端的地址信息。
  struct sockaddr_in m_servaddr;    // 服务端的地址信息。
public:
  int  m_listenfd;   // 服务端用于监听的socket。
  int  m_connfd;     // 客户端连接上来的socket。
  bool m_btimeout;   // 调用Read方法时,失败的原因是否是超时:true-超时,false-未超时。
  int  m_buflen;     // 调用Read方法后,接收到的报文的大小,单位:字节。

  CTcpServer();  // 构造函数。

  // 服务端初始化。
  // port:指定服务端用于监听的端口。
  // 返回值:true-成功;false-失败,一般情况下,只要port设置正确,没有被占用,初始化都会成功。
  bool InitServer(const unsigned int port,const int backlog=5); 

  // 阻塞等待客户端的连接请求。
  // 返回值:true-有新的客户端已连接上来,false-失败,Accept被中断,如果Accept失败,可以重新Accept。
  bool Accept();

  // 获取客户端的ip地址。
  // 返回值:客户端的ip地址,如"192.168.1.100"。
  char *GetIP();

  // 接收客户端发送过来的数据。
  // buffer:接收数据缓冲区的地址,数据的长度存放在m_buflen成员变量中。
  // itimeout:等待数据的超时时间,单位:秒,缺省值是0-无限等待。
  // 返回值:true-成功;false-失败,失败有两种情况:1)等待超时,成员变量m_btimeout的值被设置为true;2)socket连接已不可用。
  bool Read(char *buffer,const int itimeout=0);

  // 向客户端发送数据。
  // buffer:待发送数据缓冲区的地址。
  // ibuflen:待发送数据的大小,单位:字节,缺省值为0,如果发送的是ascii字符串,ibuflen取0,如果是二进制流数据,ibuflen为二进制数据块的大小。
  // 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
  bool Write(const char *buffer,const int ibuflen=0);

  // 关闭监听的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,char *buffer,int *ibuflen,const int itimeout=0);

// 向socket的对端发送数据。
// sockfd:可用的socket连接。
// buffer:待发送数据缓冲区的地址。
// ibuflen:待发送数据的字节数,如果发送的是ascii字符串,ibuflen填0或字符串的长度,
//          如果是二进制流数据,ibuflen为二进制数据块的大小。
// 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
bool TcpWrite(const int sockfd,const char *buffer,const int ibuflen=0);

// 从已经准备好的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。
void CloseIOAndSignal(bool bCloseIO=false);

// 信号量。
class CSEM
{
private:
  union semun  // 用于信号量操作的共同体。
  {
    int val;
    struct semid_ds *buf;
    unsigned short  *arry;
  };

  int   m_semid;         // 信号量描述符。

  // 如果把sem_flg设置为SEM_UNDO,操作系统将跟踪进程对信号量的修改情况,
  // 在全部修改过信号量的进程(正常或异常)终止后,操作系统将把信号量恢
  // 复为初始值(就像撤消了全部进程对信号的操作)。
  // 如果信号量用于表示可用资源的数量(不变的),设置为SEM_UNDO更合适。
  // 如果信号量用于生产消费者模型,设置为0更合适。
  // 注意,网上查到的关于sem_flg的用法基本上是错的,一定要自己动手多测试。
  short m_sem_flg;
public:
  CSEM();
  // 如果信号量已存在,获取信号量;如果信号量不存在,则创建它并初始化为value。
  bool init(key_t key,unsigned short value=1,short sem_flg=SEM_UNDO); 
  bool P(short sem_op=-1); // 信号量的P操作。
  bool V(short sem_op=1);  // 信号量的V操作。
  int  value();            // 获取信号量的值,成功返回信号量的值,失败返回-1。
  bool destroy();          // 销毁信号量。
 ~CSEM();
};

// 进程心跳信息的结构体。
struct st_procinfo
{
  int    pid;         // 进程id。
  char   pname[51];   // 进程名称,可以为空。
  int    timeout;     // 超时时间,单位:秒。
  time_t atime;       // 最后一次心跳的时间,用整数表示。
};

#define MAXNUMP     1000    // 最大的进程数量。
#define SHMKEYP   0x5095    // 共享内存的key。
#define SEMKEYP   0x5095    // 信号量的key。

// 查看共享内存:  ipcs -m
// 删除共享内存:  ipcrm -m shmid
// 查看信号量:    ipcs -s
// 删除信号量:    ipcrm sem semid

// 进程心跳操作类。
class CPActive
{
private:
  CSEM m_sem;                 // 用于给共享内存加锁的信号量id。
  int  m_shmid;               // 共享内存的id。
  int  m_pos;                 // 当前进程在共享内存进程组中的位置。
  st_procinfo *m_shm;         // 指向共享内存的地址空间。

public:
  CPActive();  // 初始化成员变量。

  // 把当前进程的心跳信息加入共享内存进程组中。
  bool AddPInfo(const int timeout,const char *pname=0,CLogFile *logfile=0);

  // 更新共享内存进程组中当前进程的心跳时间。
  bool UptATime();

  ~CPActive();  // 从共享内存中删除当前进程的心跳记录。
};

#endif

实现

/*****************************************************************************************/
/*   程序名:_public.cpp,此程序是开发框架公用函数和类的定义文件。                       */
/*   作者:吴从周                                                                        */
/*****************************************************************************************/

#include "_public.h"  

// 安全的strcpy函数。
// dest:目标字符串,不需要初始化,在STRCPY函数中有初始化代码。
// destlen:目标字符串dest占用内存的大小。
// src:原字符串。
// 返回值:目标字符串dest的地址。
// 注意,超出dest容量的内容将丢弃。
char *STRCPY(char* dest,const size_t destlen,const char* src)
{
  if (dest==0) return 0;    // 判断空指针。
  memset(dest,0,destlen);   // 初始化dest。
  // memset(dest,0,sizeof(dest));   // 不能这么写,在64位系统中,sizeof(dest)永远是8。
  if (src==0) return dest;

  if (strlen(src)>destlen-1) strncpy(dest,src,destlen-1); 
  else strcpy(dest,src);

  return dest;
}

// 安全的strncpy函数。
// dest:目标字符串,不需要初始化,在STRCPY函数中有初始化代码。
// destlen:目标字符串dest占用内存的大小。
// src:原字符串。
// n:待复制的字节数。
// 返回值:目标字符串dest的地址。
// 注意,超出dest容量的内容将丢弃。
char *STRNCPY(char* dest,const size_t destlen,const char* src,size_t n)
{
  if (dest==0) return 0;    // 判断空指针。
  memset(dest,0,destlen);   // 初始化dest。
  // memset(dest,0,sizeof(dest));   // 不能这么写,在64位系统中,sizeof(dest)永远是8。
  if (src==0) return dest;

  if (n>destlen-1) strncpy(dest,src,destlen-1); 
  else strncpy(dest,src,n);

  return dest;
}

// 安全的strcat函数。
// dest:目标字符串,注意,如果dest从未使过,那么需要至少一次初始化。
// destlen:目标字符串dest占用内存的大小。
// src:待追加字符串。
// 返回值:目标字符串dest的地址。
// 注意,超出dest容量的内容将丢弃。
char *STRCAT(char* dest,const size_t destlen,const char* src)
{
  if (dest==0) return 0;    // 判断空指针。
  if (src==0) return dest;

  unsigned int left=destlen-1-strlen(dest);

  if (strlen(src)>left) { strncat(dest,src,left); dest[destlen-1]=0; }
  else strcat(dest,src);

  return dest;
}

// 安全的strncat函数。
// dest:目标字符串,注意,如果dest从未使过,那么需要至少一次初始化。
// destlen:目标字符串dest占用内存的大小。
// src:待追加字符串。
// n:待追加的字节数。
// 返回值:目标字符串dest的地址。
// 注意,超出dest容量的内容将丢弃。
char *STRNCAT(char* dest,const size_t destlen,const char* src,size_t n)
{
  if (dest==0) return 0;    // 判断空指针。
  if (src==0) return dest;

  size_t left=destlen-1-strlen(dest);

  if (n>left) { strncat(dest,src,left); dest[destlen-1]=0; }
  else strncat(dest,src,n);

  return dest;
}

// 安全的sprintf函数。
// 将可变参数(...)按照fmt描述的格式输出到dest字符串中。
// dest:输出字符串,不需要初始化,在SPRINTF函数中会对它进行初始化。
// destlen:输出字符串dest占用内存的大小,如果格式化后的字符串内容的长度大于destlen-1,后面的内容将丢弃。
// fmt:格式控制描述。
// ...:填充到格式控制描述fmt中的参数。
// 返回值:格式化后的内容的长度,程序员一般不关心返回值。
int SPRINTF(char *dest,const size_t destlen,const char *fmt,...)
{
  if (dest==0) return -1;    // 判断空指针。

  memset(dest,0,destlen);
  // memset(dest,0,sizeof(dest));   // 不能这么写,在64位系统中,sizeof(dest)永远是8。

  va_list arg;
  va_start(arg,fmt);
  int ret=vsnprintf(dest,destlen,fmt,arg );
  va_end(arg);

  return ret;
}

// 安全的snprintf函数。
// 将可变参数(...)按照fmt描述的格式输出到dest字符串中。
// dest:输出字符串,不需要初始化,在SNPRINTF函数中会对它进行初始化。
// destlen:输出字符串dest占用内存的大小,如果格式化后的字符串内容的长度大于destlen-1,后面的内容将丢弃。
// n:把格式化后的字符串截取n-1存放到dest中,如果n>destlen,则取destlen-1。
// fmt:格式控制描述。
// ...:填充到格式控制描述fmt中的参数。
// 返回值:格式化后的内容的长度,程序员一般不关心返回值。
int SNPRINTF(char *dest,const size_t destlen,size_t n,const char *fmt,...)
{
  if (dest==0) return -1;    // 判断空指针。

  memset(dest,0,destlen);
  // memset(dest,0,sizeof(dest));   // 不能这么写,在64位系统中,sizeof(dest)永远是8。

  int len=n;
  if (n>destlen) len=destlen;

  va_list arg;
  va_start(arg,fmt);
  int ret=vsnprintf(dest,len,fmt,arg );
  va_end(arg);

  return ret;
}

// 删除字符串左边指定的字符。
// str:待处理的字符串。
// chr:需要删除的字符。
void DeleteLChar(char *str,const char chr)
{
  if (str == 0) return;
  if (strlen(str) == 0) return;

  char strTemp[strlen(str)+1];

  int iTemp=0;

  memset(strTemp,0,sizeof(strTemp));
  strcpy(strTemp,str);

  while ( strTemp[iTemp] == chr )  iTemp++;

  memset(str,0,strlen(str)+1);

  strcpy(str,strTemp+iTemp);

  return;
}

// 删除字符串右边指定的字符。
// str:待处理的字符串。
// chr:需要删除的字符。
void DeleteRChar(char *str,const char chr)
{
  if (str == 0) return;
  if (strlen(str) == 0) return;

  int istrlen = strlen(str);

  while (istrlen>0)
  {
    if (str[istrlen-1] != chr) break;

    str[istrlen-1]=0;

    istrlen--;
  }
}

// 删除字符串左右两边指定的字符。
// str:待处理的字符串。
// chr:需要删除的字符。
void DeleteLRChar(char *str,const char chr)
{
  DeleteLChar(str,chr);
  DeleteRChar(str,chr);
}

// 把字符串中的小写字母转换成大写,忽略不是字母的字符。
// str:待转换的字符串,支持char[]和string两种类型。
void ToUpper(char *str)
{
  if (str == 0) return;

  if (strlen(str) == 0) return;

  int istrlen=strlen(str);

  for (int ii=0;ii<istrlen;ii++)
  {
    if ( (str[ii] >= 'a') && (str[ii] <= 'z') ) str[ii]=str[ii] - 32;
  }
}

void ToUpper(string &str)
{
  if (str.empty()) return;

  char strtemp[str.size()+1];

  STRCPY(strtemp,sizeof(strtemp),str.c_str());

  ToUpper(strtemp);

  str=strtemp;

  return;
}

// 把字符串中的大写字母转换成小写,忽略不是字母的字符。
// str:待转换的字符串,支持char[]和string两种类型。
void ToLower(char *str)
{
  if (str == 0) return;

  if (strlen(str) == 0) return;

  int istrlen=strlen(str);

  for (int ii=0;ii<istrlen;ii++)
  {
    if ( (str[ii] >= 'A') && (str[ii] <= 'Z') ) str[ii]=str[ii] + 32;
  }
}

void ToLower(string &str)
{
  if (str.empty()) return;

  char strtemp[str.size()+1];

  STRCPY(strtemp,sizeof(strtemp),str.c_str());

  ToLower(strtemp);

  str=strtemp;

  return;
}

// 字符串替换函数
// 在字符串str中,如果存在字符串str1,就替换为字符串str2。
// str:待处理的字符串。
// str1:旧的内容。
// str2:新的内容。
// bloop:是否循环执行替换。
// 注意:
// 1、如果str2比str1要长,替换后str会变长,所以必须保证str有足够的长度,否则内存会溢出。
// 2、如果str2中包函了str1的内容,且bloop为true,存在逻辑错误,将不执行任何替换。
void UpdateStr(char *str,const char *str1,const char *str2,bool bloop)
{
  if (str == 0) return;
  if (strlen(str) == 0) return;
  if ( (str1 == 0) || (str2 == 0) ) return;

  // 如果bloop为true并且str2中包函了str1的内容,直接返回,因为会进入死循环,最终导致内存溢出。
  if ( (bloop==true) && (strstr(str2,str1)>0) ) return;

  // 尽可能分配更多的空间,但仍有可能出现内存溢出的情况,最好优化成string。
  int ilen=strlen(str)*10;
  if (ilen<1000) ilen=1000;

  char strTemp[ilen];

  char *strStart=str;

  char *strPos=0;

  while (true)
  {
    if (bloop == true)
    {
      strPos=strstr(str,str1);
    }
    else
    {
      strPos=strstr(strStart,str1);
    }

    if (strPos == 0) break;

    memset(strTemp,0,sizeof(strTemp));
    STRNCPY(strTemp,sizeof(strTemp),str,strPos-str);
    STRCAT(strTemp,sizeof(strTemp),str2);
    STRCAT(strTemp,sizeof(strTemp),strPos+strlen(str1));
    strcpy(str,strTemp);

    strStart=strPos+strlen(str2);
  }
}

// 从一个字符串中提取出数字的内容,存放到另一个字符串中。
// src:源字符串。
// dest:目标字符串。
// bsigned:是否包括符号(+和-),true-包括;false-不包括。
// bdot:是否包括小数点的圆点符号,true-包括;false-不包括。
void PickNumber(const char *src,char *dest,const bool bsigned,const bool bdot)
{
  if (dest==0) return;    // 判断空指针。
  if (src==0) { strcpy(dest,""); return; }

  char strtemp[strlen(src)+1];
  memset(strtemp,0,sizeof(strtemp));
  strcpy(strtemp,src);
  DeleteLRChar(strtemp,' ');

  int ipossrc,iposdst,ilen;
  ipossrc=iposdst=ilen=0;

  ilen=strlen(strtemp);

  for (ipossrc=0;ipossrc<ilen;ipossrc++)
  {
    if ( (bsigned==true) && (strtemp[ipossrc] == '+') )
    {
      dest[iposdst++]=strtemp[ipossrc]; continue;
    }

    if ( (bsigned==true) && (strtemp[ipossrc] == '-') )
    {
      dest[iposdst++]=strtemp[ipossrc]; continue;
    }

    if ( (bdot==true) && (strtemp[ipossrc] == '.') )
    {
      dest[iposdst++]=strtemp[ipossrc]; continue;
    }

    if (isdigit(strtemp[ipossrc])) dest[iposdst++]=strtemp[ipossrc];
  }

  dest[iposdst]=0;
}

// 正则表达式,判断一个字符串是否匹配另一个字符串。
// str:需要判断的字符串,精确表示的字符串,如文件名"_public.cpp"。
// rules:匹配规则表达式,用星号"*"表示任意字符串,多个字符串之间用半角的逗号分隔,如"*.h,*.cpp"。
// 注意,str参数不支持"*",rules参数支持"*",函数在判断str是否匹配rules的时候,会忽略字母的大小写。
bool MatchStr(const string &str,const string &rules)
{
  // 如果用于比较的字符是空的,返回false
  if (rules.size() == 0) return false;

  // 如果被比较的字符串是"*",返回true
  if (rules == "*") return true;

  int ii,jj;
  int  iPOS1,iPOS2;
  CCmdStr CmdStr,CmdSubStr;

  string strFileName,strMatchStr;

  strFileName=str;
  strMatchStr=rules;

  // 把字符串都转换成大写后再来比较
  ToUpper(strFileName);
  ToUpper(strMatchStr);

  CmdStr.SplitToCmd(strMatchStr,",");

  for (ii=0;ii<CmdStr.CmdCount();ii++)
  {
    // 如果为空,就一定要跳过,否则就会被配上
    if (CmdStr.m_vCmdStr[ii].empty() == true) continue;

    iPOS1=iPOS2=0;
    CmdSubStr.SplitToCmd(CmdStr.m_vCmdStr[ii],"*");

    for (jj=0;jj<CmdSubStr.CmdCount();jj++)
    {
      // 如果是文件名的首部
      if (jj == 0)
      {
        if (strncmp(strFileName.c_str(),CmdSubStr.m_vCmdStr[jj].c_str(),CmdSubStr.m_vCmdStr[jj].size()) != 0) break;
      }

      // 如果是文件名的尾部
      if (jj == CmdSubStr.CmdCount()-1)
      {
        if (strcmp(strFileName.c_str()+strFileName.size()-CmdSubStr.m_vCmdStr[jj].size(),CmdSubStr.m_vCmdStr[jj].c_str()) != 0) break;
      }

      iPOS2=strFileName.find(CmdSubStr.m_vCmdStr[jj],iPOS1);

      if (iPOS2 < 0) break;

      iPOS1=iPOS2+CmdSubStr.m_vCmdStr[jj].size();
    }

    if (jj==CmdSubStr.CmdCount()) return true;
  }

  return false;
}

CFile::CFile()   // 类的构造函数
{
  m_fp=0;
  m_bEnBuffer=true;
  memset(m_filename,0,sizeof(m_filename));
  memset(m_filenametmp,0,sizeof(m_filenametmp));
}

// 关闭文件指针
void CFile::Close() 
{
  if (m_fp==0) return;    // 判断空指针。

  fclose(m_fp);  // 关闭文件指针

  m_fp=0;
  memset(m_filename,0,sizeof(m_filename));

  // 如果存在临时文件,就删除它。
  if (strlen(m_filenametmp)!=0) remove(m_filenametmp);

  memset(m_filenametmp,0,sizeof(m_filenametmp));
}

// 判断文件是否已打开
bool CFile::IsOpened()
{
  if (m_fp==0) return false;    // 判断空指针。

  return true;
}

// 关闭文件指针,并删除文件
bool CFile::CloseAndRemove()
{
  if (m_fp==0) return true;    // 判断空指针。

  fclose(m_fp);  // 关闭文件指针

  m_fp=0;

  if (remove(m_filename) != 0) { memset(m_filename,0,sizeof(m_filename)); return false; }

  memset(m_filename,0,sizeof(m_filename));
  memset(m_filenametmp,0,sizeof(m_filenametmp));

  return true;
}

CFile::~CFile()   // 类的析构函数
{
  Close();
}

// 打开文件,参数与FOPEN相同,打开成功true,失败返回false
bool CFile::Open(const char *filename,const char *openmode,bool bEnBuffer)
{
  Close();

  if ( (m_fp=FOPEN(filename,openmode)) == 0 ) return false;

  memset(m_filename,0,sizeof(m_filename));

  STRNCPY(m_filename,sizeof(m_filename),filename,300);

  m_bEnBuffer=bEnBuffer;

  return true;
}

// 专为改名而打开文件,参数与fopen相同,打开成功true,失败返回false
bool CFile::OpenForRename(const char *filename,const char *openmode,bool bEnBuffer)
{
  Close();

  memset(m_filename,0,sizeof(m_filename));
  STRNCPY(m_filename,sizeof(m_filename),filename,300);
  
  memset(m_filenametmp,0,sizeof(m_filenametmp));
  SNPRINTF(m_filenametmp,sizeof(m_filenametmp),300,"%s.tmp",m_filename);

  if ( (m_fp=FOPEN(m_filenametmp,openmode)) == 0 ) return false;

  m_bEnBuffer=bEnBuffer;

  return true;
}

// 关闭文件并改名
bool CFile::CloseAndRename()
{
  if (m_fp==0) return false;    // 判断空指针。

  fclose(m_fp);  // 关闭文件指针

  m_fp=0;

  if (rename(m_filenametmp,m_filename) != 0)
  {
    remove(m_filenametmp);
    memset(m_filename,0,sizeof(m_filename));
    memset(m_filenametmp,0,sizeof(m_filenametmp));
    return false;
  }

  memset(m_filename,0,sizeof(m_filename));
  memset(m_filenametmp,0,sizeof(m_filenametmp));

  return true;
}

// 调用fprintf向文件写入数据
void CFile::Fprintf(const char *fmt,...)
{
  if ( m_fp == 0 ) return;

  va_list arg;
  va_start( arg, fmt );
  vfprintf( m_fp, fmt, arg );
  va_end( arg );

  if ( m_bEnBuffer == false ) fflush(m_fp);
}

// 调用fgets从文件中读取一行,bDelCRT=true删除换行符,false不删除,缺省为false
bool CFile::Fgets(char *buffer,const int readsize,bool bdelcrt)
{
  if ( m_fp == 0 ) return false;

  memset(buffer,0,readsize+1);  // 调用者必须保证buffer的空间足够,否则这里会内存溢出。

  if (fgets(buffer,readsize,m_fp) == 0) return false;

  if (bdelcrt==true)
  {
    DeleteRChar(buffer,'\n'); DeleteRChar(buffer,'\r');  // 如果文件是windows格式,还要删除'\r'。
  }

  return true;
}

// 从文件文件中读取一行
// strEndStr是一行数据的结束标志,如果为空,则以换行符"\n"为结束标志。
bool CFile::FFGETS(char *buffer,const int readsize,const char *endbz)
{
  if ( m_fp == 0 ) return false;

  return FGETS(m_fp,buffer,readsize,endbz);
}

// 调用fread从文件中读取数据。
size_t CFile::Fread(void *ptr, size_t size)
{
  if ( m_fp == 0 ) return -1;

  return fread(ptr,1,size,m_fp);
}

// 调用fwrite向文件中写数据
size_t CFile::Fwrite(const void *ptr, size_t size )
{
  if ( m_fp == 0 ) return -1;

  size_t tt=fwrite(ptr,1,size,m_fp);

  if ( m_bEnBuffer == false ) fflush(m_fp);

  return tt;
}


// 从文本文件中读取一行。
// fp:已打开的文件指针。
// buffer:用于存放读取的内容。
// readsize:本次打算读取的字节数,如果已经读取到了结束标志,函数返回。
// endbz:行内容结束的标志,缺省为空,表示行内容以"\n"为结束标志。
// 返回值:true-成功;false-失败,一般情况下,失败可以认为是文件已结束。
bool FGETS(const FILE *fp,char *buffer,const int readsize,const char *endbz)
{
  if ( fp == 0 ) return false;

  memset(buffer,0,readsize+1);   // 调用者必须保证buffer的空间足够,否则这里会内存溢出。

  char strline[readsize+1];

  while (true)
  {
    memset(strline,0,sizeof(strline));

    if (fgets(strline,readsize,(FILE *)fp) == 0) break;

    // 防止buffer溢出
    if ( (strlen(buffer)+strlen(strline)) >= (unsigned int)readsize ) break;

    strcat(buffer,strline);

    if (endbz == 0) return true;
    else if (strstr(strline,endbz)!= 0) return true;
    // 以上代码可以不用strstr,可以优化为只比较最后的内容。
  }

  return false;
}


CCmdStr::CCmdStr()
{
  m_vCmdStr.clear();
}

CCmdStr::CCmdStr(const string &buffer,const char *sepstr,const bool bdelspace)
{
  m_vCmdStr.clear();

  SplitToCmd(buffer,sepstr,bdelspace);
}

// 把字符串拆分到m_vCmdStr容器中。
// buffer:待拆分的字符串。
// sepstr:buffer字符串中字段内容的分隔符,注意,分隔符是字符串,如","、" "、"|"、"~!~"。
// bdelspace:是否删除拆分后的字段内容前后的空格,true-删除;false-不删除,缺省不删除。
void CCmdStr::SplitToCmd(const string &buffer,const char *sepstr,const bool bdelspace)
{
  // 清除所有的旧数据
  m_vCmdStr.clear();

  int iPOS=0;
  string srcstr,substr;

  srcstr=buffer;

  while ( (iPOS=srcstr.find(sepstr)) >= 0)
  {
    substr=srcstr.substr(0,iPOS);

    if (bdelspace == true)
    {
      char str[substr.length()+1];
      STRCPY(str,sizeof(str),substr.c_str());

      DeleteLRChar(str,' ');

      substr=str;
    }

    m_vCmdStr.push_back(substr);

    iPOS=iPOS+strlen(sepstr);

    srcstr=srcstr.substr(iPOS,srcstr.size()-iPOS);

  }

  substr=srcstr;

  if (bdelspace == true)
  {
    char str[substr.length()+1];
    STRCPY(str,sizeof(str),substr.c_str());

    DeleteLRChar(str,' ');

    substr=str;
  }

  m_vCmdStr.push_back(substr);

  return;
}

int CCmdStr::CmdCount()
{
  return m_vCmdStr.size();
}

bool CCmdStr::GetValue(const int inum,char *value,const int ilen)
{
  if ( (inum>=(int)m_vCmdStr.size()) || (value==0) ) return false;

  if (ilen>0) memset(value,0,ilen+1);   // 调用者必须保证value的空间足够,否则这里会内存溢出。

  if ( (m_vCmdStr[inum].length()<=(unsigned int)ilen) || (ilen==0) )
  {
    strcpy(value,m_vCmdStr[inum].c_str());
  }
  else
  {
    strncpy(value,m_vCmdStr[inum].c_str(),ilen); value[ilen]=0;
  }

  return true;
}

bool CCmdStr::GetValue(const int inum,int *value)
{
  if ( (inum>=(int)m_vCmdStr.size()) || (value==0) ) return false;

  (*value) = 0;

  if (inum >= (int)m_vCmdStr.size()) return false;

  (*value) = atoi(m_vCmdStr[inum].c_str());

  return true;
}

bool CCmdStr::GetValue(const int inum,unsigned int *value)
{
  if ( (inum>=(int)m_vCmdStr.size()) || (value==0) ) return false;

  (*value) = 0;

  if (inum >= (int)m_vCmdStr.size()) return false;

  (*value) = atoi(m_vCmdStr[inum].c_str());

  return true;
}


bool CCmdStr::GetValue(const int inum,long *value)
{
  if ( (inum>=(int)m_vCmdStr.size()) || (value==0) ) return false;

  (*value) = 0;

  if (inum >= (int)m_vCmdStr.size()) return false;

  (*value) = atol(m_vCmdStr[inum].c_str());

  return true;
}

bool CCmdStr::GetValue(const int inum,unsigned long *value)
{
  if ( (inum>=(int)m_vCmdStr.size()) || (value==0) ) return false;

  (*value) = 0;

  if (inum >= (int)m_vCmdStr.size()) return false;

  (*value) = atol(m_vCmdStr[inum].c_str());

  return true;
}

bool CCmdStr::GetValue(const int inum,double *value)
{
  if ( (inum>=(int)m_vCmdStr.size()) || (value==0) ) return false;

  (*value) = 0;

  if (inum >= (int)m_vCmdStr.size()) return false;

  (*value) = (double)atof(m_vCmdStr[inum].c_str());

  return true;
}

bool CCmdStr::GetValue(const int inum,bool *value)
{
  if ( (inum>=(int)m_vCmdStr.size()) || (value==0) ) return false;

  (*value) = 0;

  if (inum >= (int)m_vCmdStr.size()) return false;

  char strTemp[11];
  memset(strTemp,0,sizeof(strTemp));
  strncpy(strTemp,m_vCmdStr[inum].c_str(),10);

  ToUpper(strTemp);  // 转换为大写来判断。
  if (strcmp(strTemp,"TRUE")==0) (*value)=true; 

  return true;
}

CCmdStr::~CCmdStr()
{
  m_vCmdStr.clear();
}

bool GetXMLBuffer(const char *xmlbuffer,const char *fieldname,char *value,const int ilen)
{
  if (value==0) return false;    // 判断空指针。

  if (ilen>0) memset(value,0,ilen+1);   // 调用者必须保证value的空间足够,否则这里会内存溢出。

  char *start=0,*end=0;
  char m_SFieldName[51],m_EFieldName[51];

  int m_NameLen = strlen(fieldname);

  SNPRINTF(m_SFieldName,sizeof(m_SFieldName),50,"<%s>",fieldname);
  SNPRINTF(m_EFieldName,sizeof(m_EFieldName),50,"</%s>",fieldname);

  start=0; end=0;

  start = (char *)strstr(xmlbuffer,m_SFieldName);

  if (start != 0)
  {
    end   = (char *)strstr(start,m_EFieldName);
  }

  if ((start==0) || (end == 0))
  {
    return false;
  }

  int   m_ValueLen = end - start - m_NameLen - 2 ;

  if ( ((m_ValueLen) <= ilen) || (ilen == 0) )
  {
    strncpy(value,start+m_NameLen+2,m_ValueLen); value[m_ValueLen]=0;
  }
  else
  {
    strncpy(value,start+m_NameLen+2,ilen); value[ilen]=0;
  }

  DeleteLRChar(value,' ');

  return true;
}

bool GetXMLBuffer(const char *xmlbuffer,const char *fieldname,bool *value)
{
  if (value==0) return false;    // 判断空指针。

  (*value) = false;

  char strTemp[51];

  memset(strTemp,0,sizeof(strTemp));

  if (GetXMLBuffer(xmlbuffer,fieldname,strTemp,10) == true)
  {
    ToUpper(strTemp);  // 转换为大写来判断。
    if (strcmp(strTemp,"TRUE")==0) { (*value)=true; return true; }
  }

  return false;
}

bool GetXMLBuffer(const char *xmlbuffer,const char *fieldname,int *value)
{
  if (value==0) return false;    // 判断空指针。

  (*value) = 0;

  char strTemp[51];

  memset(strTemp,0,sizeof(strTemp));

  if (GetXMLBuffer(xmlbuffer,fieldname,strTemp,50) == true)
  {
    (*value) = atoi(strTemp); return true;
  }

  return false;
}

bool GetXMLBuffer(const char *xmlbuffer,const char *fieldname,unsigned int *value)
{
  if (value==0) return false;    // 判断空指针。

  (*value) = 0;

  char strTemp[51];

  memset(strTemp,0,sizeof(strTemp));

  if (GetXMLBuffer(xmlbuffer,fieldname,strTemp,50) == true)
  {
    (*value) = (unsigned int)atoi(strTemp); return true;
  }

  return false;
}

bool GetXMLBuffer(const char *xmlbuffer,const char *fieldname,long *value)
{
  if (value==0) return false;    // 判断空指针。

  (*value) = 0;

  char strTemp[51];

  memset(strTemp,0,sizeof(strTemp));

  if (GetXMLBuffer(xmlbuffer,fieldname,strTemp,50) == true)
  {
    (*value) = atol(strTemp); return true;
  }

  return false;
}

bool GetXMLBuffer(const char *xmlbuffer,const char *fieldname,unsigned long *value)
{
  if (value==0) return false;    // 判断空指针。

  (*value) = 0;

  char strTemp[51];

  memset(strTemp,0,sizeof(strTemp));

  if (GetXMLBuffer(xmlbuffer,fieldname,strTemp,50) == true)
  {
    (*value) = (unsigned long)atol(strTemp); return true;
  }

  return false;
}

bool GetXMLBuffer(const char *xmlbuffer,const char *fieldname,double *value)
{
  if (value==0) return false;    // 判断空指针。

  (*value) = 0;

  char strTemp[51];

  memset(strTemp,0,sizeof(strTemp));

  if (GetXMLBuffer(xmlbuffer,fieldname,strTemp,50) == true)
  {
    (*value) = atof(strTemp); return true;
  }

  return false;
}

// 把整数表示的时间转换为字符串表示的时间。
// ltime:整数表示的时间。
// stime:字符串表示的时间。
// fmt:输出字符串时间stime的格式,与LocalTime函数的fmt参数相同,如果fmt的格式不正确,stime将为空。
void timetostr(const time_t ltime,char *stime,const char *fmt)
{
  if (stime==0) return;    // 判断空指针。

  strcpy(stime,"");

  struct tm sttm = *localtime ( &ltime );
  // struct tm sttm; localtime_r(&ltime,&sttm); 

  sttm.tm_year=sttm.tm_year+1900;
  sttm.tm_mon++;

  if (fmt==0)
  {
    snprintf(stime,20,"%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;
  }

  if (strcmp(fmt,"yyyy-mm-dd hh24:mi:ss") == 0)
  {
    snprintf(stime,20,"%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;
  }

  if (strcmp(fmt,"yyyy-mm-dd hh24:mi") == 0)
  {
    snprintf(stime,17,"%04u-%02u-%02u %02u:%02u",sttm.tm_year,
                    sttm.tm_mon,sttm.tm_mday,sttm.tm_hour,
                    sttm.tm_min);
    return;
  }

  if (strcmp(fmt,"yyyy-mm-dd hh24") == 0)
  {
    snprintf(stime,14,"%04u-%02u-%02u %02u",sttm.tm_year,
                    sttm.tm_mon,sttm.tm_mday,sttm.tm_hour);
    return;
  }

  if (strcmp(fmt,"yyyy-mm-dd") == 0)
  {
    snprintf(stime,11,"%04u-%02u-%02u",sttm.tm_year,sttm.tm_mon,sttm.tm_mday); 
    return;
  }

  if (strcmp(fmt,"yyyy-mm") == 0)
  {
    snprintf(stime,8,"%04u-%02u",sttm.tm_year,sttm.tm_mon); 
    return;
  }

  if (strcmp(fmt,"yyyymmddhh24miss") == 0)
  {
    snprintf(stime,15,"%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;
  }

  if (strcmp(fmt,"yyyymmddhh24mi") == 0)
  {
    snprintf(stime,13,"%04u%02u%02u%02u%02u",sttm.tm_year,
                    sttm.tm_mon,sttm.tm_mday,sttm.tm_hour,
                    sttm.tm_min);
    return;
  }

  if (strcmp(fmt,"yyyymmddhh24") == 0)
  {
    snprintf(stime,11,"%04u%02u%02u%02u",sttm.tm_year,
                    sttm.tm_mon,sttm.tm_mday,sttm.tm_hour);
    return;
  }

  if (strcmp(fmt,"yyyymmdd") == 0)
  {
    snprintf(stime,9,"%04u%02u%02u",sttm.tm_year,sttm.tm_mon,sttm.tm_mday); 
    return;
  }

  if (strcmp(fmt,"hh24miss") == 0)
  {
    snprintf(stime,7,"%02u%02u%02u",sttm.tm_hour,sttm.tm_min,sttm.tm_sec); 
    return;
  }

  if (strcmp(fmt,"hh24mi") == 0)
  {
    snprintf(stime,5,"%02u%02u",sttm.tm_hour,sttm.tm_min); 
    return;
  }

  if (strcmp(fmt,"hh24") == 0)
  {
    snprintf(stime,3,"%02u",sttm.tm_hour); 
    return;
  }

  if (strcmp(fmt,"mi") == 0)
  {
    snprintf(stime,3,"%02u",sttm.tm_min); 
    return;
  }
}


/*
  取操作系统的时间,并把整数表示的时间转换为字符串表示的格式。
  stime:用于存放获取到的时间字符串。
  timetvl:时间的偏移量,单位:秒,0是缺省值,表示当前时间,30表示当前时间30秒之后的时间点,-30表示当前时间30秒之前的时间点。
  fmt:输出时间的格式,缺省是"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)以上列出了常用的时间格式,如果不能满足你应用开发的需求,请修改源代码增加更多的格式支持;
    3)调用函数的时候,如果fmt与上述格式都匹配,stime的内容将为空。
*/
void LocalTime(char *stime,const char *fmt,const int timetvl)
{
  if (stime==0) return;    // 判断空指针。

  time_t  timer;

  time( &timer ); timer=timer+timetvl;

  timetostr(timer,stime,fmt);
}


CLogFile::CLogFile(const long MaxLogSize)
{
  m_tracefp = 0;
  memset(m_filename,0,sizeof(m_filename));
  memset(m_openmode,0,sizeof(m_openmode));
  m_bBackup=true;
  m_bEnBuffer=false;
  m_MaxLogSize=MaxLogSize;
  if (m_MaxLogSize<10) m_MaxLogSize=10;

  // pthread_pin_init(&spin,0);  // 初学暂时不要关心这行代码。
}

CLogFile::~CLogFile()
{
  Close();

  // pthread_spin_destroy(&spin);  // 初学暂时不要关心这行代码。
}

void CLogFile::Close()
{
  if (m_tracefp != 0) { fclose(m_tracefp); m_tracefp=0; }

  memset(m_filename,0,sizeof(m_filename));
  memset(m_openmode,0,sizeof(m_openmode));
  m_bBackup=true;
  m_bEnBuffer=false;
}

// 打开日志文件。
// filename:日志文件名,建议采用绝对路径,如果文件名中的目录不存在,就先创建目录。
// openmode:日志文件的打开方式,与fopen库函数打开文件的方式相同,缺省值是"a+"。
// bBackup:是否自动切换,true-切换,false-不切换,在多进程的服务程序中,如果多个进行共用一个日志文件,bBackup必须为false。
// bEnBuffer:是否启用文件缓冲机制,true-启用,false-不启用,如果启用缓冲区,那么写进日志文件中的内容不会立即写入文件,缺省是不启用。
bool CLogFile::Open(const char *filename,const char *openmode,bool bBackup,bool bEnBuffer)
{
  // 如果文件指针是打开的状态,先关闭它。
  Close();

  STRCPY(m_filename,sizeof(m_filename),filename);
  m_bEnBuffer=bEnBuffer;
  m_bBackup=bBackup;
  if (openmode==0) STRCPY(m_openmode,sizeof(m_openmode),"a+");
  else STRCPY(m_openmode,sizeof(m_openmode),openmode);

  if ((m_tracefp=FOPEN(m_filename,m_openmode)) == 0) return false;

  return true;
}

// 如果日志文件大于100M,就把当前的日志文件备份成历史日志文件,切换成功后清空当前日志文件的内容。
// 备份后的文件会在日志文件名后加上日期时间。
// 注意,在多进程的程序中,日志文件不可切换,多线的程序中,日志文件可以切换。
bool CLogFile::BackupLogFile()
{
  if (m_tracefp == 0) return false;

  // 不备份
  if (m_bBackup == false) return true;

  //fseek(m_tracefp,0,2);

  if (ftell(m_tracefp) > m_MaxLogSize*1024*1024)
  {
    fclose(m_tracefp); m_tracefp=0;

    char strLocalTime[21];
    memset(strLocalTime,0,sizeof(strLocalTime));
    LocalTime(strLocalTime,"yyyymmddhh24miss");

    char bak_filename[301];
    SNPRINTF(bak_filename,sizeof(bak_filename),300,"%s.%s",m_filename,strLocalTime);
    rename(m_filename,bak_filename);

    if ((m_tracefp=FOPEN(m_filename,m_openmode)) == 0) return false;
  }

  return true;
}

// 把内容写入日志文件,fmt是可变参数,使用方法与printf库函数相同。
// Write方法会写入当前的时间,WriteEx方法不写时间。
bool CLogFile::Write(const char *fmt,...)
{
  if (m_tracefp == 0) return false;

  // pthread_spin_lock(&spin);  // 初学暂时不要关心这行代码。

  if (BackupLogFile() == false) return false;

  char strtime[20]; LocalTime(strtime);
  va_list ap;
  va_start(ap,fmt);
  fprintf(m_tracefp,"%s ",strtime);
  vfprintf(m_tracefp,fmt,ap);
  va_end(ap);

  if (m_bEnBuffer==false) fflush(m_tracefp);

  // pthread_spin_unlock(&spin);  // 初学暂时不要关心这行代码。

  return true;
}

// 把内容写入日志文件,fmt是可变参数,使用方法与printf库函数相同。
// Write方法会写入当前的时间,WriteEx方法不写时间。
bool CLogFile::WriteEx(const char *fmt,...)
{
  if (m_tracefp == 0) return false;

  // pthread_spin_lock(&spin);  // 初学暂时不要关心这行代码。

  va_list ap;
  va_start(ap,fmt);
  vfprintf(m_tracefp,fmt,ap);
  va_end(ap);

  if (m_bEnBuffer==false) fflush(m_tracefp);

  // pthread_spin_unlock(&spin);  // 初学暂时不要关心这行代码。

  return true;
}

CIniFile::CIniFile()
{
  
}

bool CIniFile::LoadFile(const char *filename)
{
  m_xmlbuffer.clear();

  CFile File;

  if ( File.Open(filename,"r") == false) return false;

  char strLine[501];

  while (true)
  {
    memset(strLine,0,sizeof(strLine));

    if (File.FFGETS(strLine,500) == false) break;

    m_xmlbuffer=m_xmlbuffer+strLine;
  }

  if (m_xmlbuffer.length() < 10) return false;

  return true;
}

bool CIniFile::GetValue(const char *fieldname,bool   *value)
{
  return GetXMLBuffer(m_xmlbuffer.c_str(),fieldname,value);
}

bool CIniFile::GetValue(const char *fieldname,char *value,int ilen)
{
  return GetXMLBuffer(m_xmlbuffer.c_str(),fieldname,value,ilen);
}

bool CIniFile::GetValue(const char *fieldname,int *value)
{
  return GetXMLBuffer(m_xmlbuffer.c_str(),fieldname,value);
}

bool CIniFile::GetValue(const char *fieldname,unsigned int *value)
{
  return GetXMLBuffer(m_xmlbuffer.c_str(),fieldname,value);
}

bool CIniFile::GetValue(const char *fieldname,long *value)
{
  return GetXMLBuffer(m_xmlbuffer.c_str(),fieldname,value);
}

bool CIniFile::GetValue(const char *fieldname,unsigned long *value)
{
  return GetXMLBuffer(m_xmlbuffer.c_str(),fieldname,value);
}

bool CIniFile::GetValue(const char *fieldname,double *value)
{
  return GetXMLBuffer(m_xmlbuffer.c_str(),fieldname,value);
}

// 关闭全部的信号和输入输出
void CloseIOAndSignal(bool bCloseIO)
{
  int ii=0;

  for (ii=0;ii<64;ii++)
  {
    if (bCloseIO==true) close(ii);

    signal(ii,SIG_IGN); 
  }
}

// 根据绝对路径的文件名或目录名逐级的创建目录。
// pathorfilename:绝对路径的文件名或目录名。
// bisfilename:说明pathorfilename的类型,true-pathorfilename是文件名,否则是目录名,缺省值为true。
// 返回值:true-创建成功,false-创建失败,如果返回失败,原因有大概有三种情况:1)权限不足; 2)pathorfilename参数不是合法的文件名或目录名;3)磁盘空间不足。
bool MKDIR(const char *filename,bool bisfilename)
{
  // 检查目录是否存在,如果不存在,逐级创建子目录
  char strPathName[301];

  int ilen=strlen(filename);

  for (int ii=1; ii<ilen;ii++)
  {
    if (filename[ii] != '/') continue;

    STRNCPY(strPathName,sizeof(strPathName),filename,ii);

    if (access(strPathName,F_OK) == 0) continue; // 如果目录已存在,continue

    if (mkdir(strPathName,0755) != 0) return false;  // 如果目录不存在,创建它。
  }

  if (bisfilename==false)
  {
    if (access(filename,F_OK) != 0)
    {
      if (mkdir(filename,0755) != 0) return false;
    }
  }

  return true;
}

// 打开文件。
// FOPEN函数调用fopen库函数打开文件,如果文件名中包含的目录不存在,就创建目录。
// FOPEN函数的参数和返回值与fopen函数完全相同。
// 在应用开发中,用FOPEN函数代替fopen库函数。
FILE *FOPEN(const char *filename,const char *mode)
{
  if (MKDIR(filename) == false) return 0;

  return fopen(filename,mode);
}

// 获取文件的大小。
// filename:待获取的文件名,建议采用绝对路径的文件名。
// 返回值:如果文件不存在或没有访问权限,返回-1,成功返回文件的大小,单位是字节。
int FileSize(const char *filename)
{
  struct stat st_filestat;

  if (stat(filename,&st_filestat) < 0) return -1;

  return st_filestat.st_size;
}

// 重置文件的修改时间属性。
// filename:待重置的文件名,建议采用绝对路径的文件名。
// stime:字符串表示的时间,格式不限,但一定要包括yyyymmddhh24miss,一个都不能少。
// 返回值:true-成功;false-失败,失败的原因保存在errno中。
bool UTime(const char *filename,const char *mtime)
{
  struct utimbuf stutimbuf;

  stutimbuf.actime=stutimbuf.modtime=strtotime(mtime);

  if (utime(filename,&stutimbuf)!=0) return false;

  return true;
}

// 把字符串表示的时间转换为整数表示的时间。
// stime:字符串表示的时间,格式不限,但一定要包括yyyymmddhh24miss,一个都不能少。
// 返回值:整数表示的时间,如果stime的格式不正确,返回-1。
time_t strtotime(const char *stime)
{
  char strtime[21],yyyy[5],mm[3],dd[3],hh[3],mi[3],ss[3];
  memset(strtime,0,sizeof(strtime));
  memset(yyyy,0,sizeof(yyyy));
  memset(mm,0,sizeof(mm));
  memset(dd,0,sizeof(dd));
  memset(hh,0,sizeof(hh));
  memset(mi,0,sizeof(mi));
  memset(ss,0,sizeof(ss));

  PickNumber(stime,strtime,false,false);

  if (strlen(strtime) != 14) return -1;

  strncpy(yyyy,strtime,4);
  strncpy(mm,strtime+4,2);
  strncpy(dd,strtime+6,2);
  strncpy(hh,strtime+8,2);
  strncpy(mi,strtime+10,2);
  strncpy(ss,strtime+12,2);

  struct tm time_str;

  time_str.tm_year = atoi(yyyy) - 1900;
  time_str.tm_mon = atoi(mm) - 1;
  time_str.tm_mday = atoi(dd);
  time_str.tm_hour = atoi(hh);
  time_str.tm_min = atoi(mi);
  time_str.tm_sec = atoi(ss);
  time_str.tm_isdst = 0;

  return mktime(&time_str);
}

// 把字符串表示的时间加上一个偏移的秒数后得到一个新的字符串表示的时间。
// in_stime:输入的字符串格式的时间。
// out_stime:输出的字符串格式的时间。
// timetvl:需要偏移的秒数,正数往后偏移,负数往前偏移。
// fmt:输出字符串时间out_stime的格式,与LocalTime函数的fmt参数相同。
// 注意:in_stime和out_stime参数可以是同一个变量的地址,如果调用失败,out_stime的内容会清空。
// 返回值:true-成功,false-失败,如果返回失败,可以认为是in_stime的格式不正确。
bool AddTime(const char *in_stime,char *out_stime,const int timetvl,const char *fmt)
{
  if ( (in_stime==0) || (out_stime==0) ) return false;    // 判断空指针。

  time_t  timer;
  if ( (timer=strtotime(in_stime))==-1) { strcpy(out_stime,""); return false; }

  timer=timer+timetvl;

  strcpy(out_stime,"");

  timetostr(timer,out_stime,fmt);

  return true;
}

// 获取文件的时间。
// filename:待获取的文件名,建议采用绝对路径的文件名。
// mtime:用于存放文件的时间,即stat结构体的st_mtime。
// fmt:设置时间的输出格式,与LocalTime函数相同,但缺省是"yyyymmddhh24miss"。
// 返回值:如果文件不存在或没有访问权限,返回false,成功返回true。
bool FileMTime(const char *filename,char *mtime,const char *fmt)
{
  // 判断文件是否存在。
  struct stat st_filestat;

  if (stat(filename,&st_filestat) < 0) return false;

  char strfmt[25];
  memset(strfmt,0,sizeof(strfmt));
  if (fmt==0) strcpy(strfmt,"yyyymmddhh24miss");
  else strcpy(strfmt,fmt);

  timetostr(st_filestat.st_mtime,mtime,strfmt);

  return true;
}


CDir::CDir()
{
  m_pos=0;

  STRCPY(m_DateFMT,sizeof(m_DateFMT),"yyyy-mm-dd hh24:mi:ss");

  m_vFileName.clear();

  initdata();
}

void CDir::initdata()
{
  memset(m_DirName,0,sizeof(m_DirName));
  memset(m_FileName,0,sizeof(m_FileName));
  memset(m_FullFileName,0,sizeof(m_FullFileName));
  m_FileSize=0;
  memset(m_CreateTime,0,sizeof(m_CreateTime));
  memset(m_ModifyTime,0,sizeof(m_ModifyTime));
  memset(m_AccessTime,0,sizeof(m_AccessTime));
}

// 设置文件时间的格式,支持"yyyy-mm-dd hh24:mi:ss"和"yyyymmddhh24miss"两种,缺省是前者。
void CDir::SetDateFMT(const char *in_DateFMT)
{
  memset(m_DateFMT,0,sizeof(m_DateFMT));
  STRCPY(m_DateFMT,sizeof(m_DateFMT),in_DateFMT);
}

// 打开目录,获取目录中的文件列表信息,存放于m_vFileName容器中。
// in_DirName,待打开的目录名。
// in_MatchStr,待获取文件名的匹配规则,不匹配的文件被忽略。
// in_MaxCount,获取文件的最大数量,缺省值为10000个。
// bAndChild,是否打开各级子目录,缺省值为false-不打开子目录。
// bSort,是否对获取到的文件列表(即m_vFileName容器中的内容)进行排序,缺省值为false-不排序。
// 返回值:如果in_DirName参数指定的目录不存在,OpenDir方法会创建该目录,如果创建失败,返回false,还有,如果当前用户对in_DirName目录下的子目录没有读取权限也会返回false,其它正常情况下都会返回true。
bool CDir::OpenDir(const char *in_DirName,const char *in_MatchStr,const unsigned int in_MaxCount,const bool bAndChild,bool bSort)
{
  m_pos=0;
  m_vFileName.clear();

  // 如果目录不存在,就创建该目录
  if (MKDIR(in_DirName,false) == false) return false;

  bool bRet=_OpenDir(in_DirName,in_MatchStr,in_MaxCount,bAndChild);

  if (bSort==true)
  {
    sort(m_vFileName.begin(), m_vFileName.end());
  }

  return bRet;
}

// 这是一个递归函数,用于OpenDir()的调用,在CDir类的外部不需要调用它。
bool CDir::_OpenDir(const char *in_DirName,const char *in_MatchStr,const unsigned int in_MaxCount,const bool bAndChild)
{
  DIR *dir;

  if ( (dir=opendir(in_DirName)) == 0 ) return false;

  char strTempFileName[3001];

  struct dirent *st_fileinfo;
  struct stat st_filestat;

  while ((st_fileinfo=readdir(dir)) != 0)
  {
    // 以"."打头的文件不处理
    if (st_fileinfo->d_name[0]=='.') continue;
        
    SNPRINTF(strTempFileName,sizeof(strTempFileName),300,"%s//%s",in_DirName,st_fileinfo->d_name);

    UpdateStr(strTempFileName,"//","/");

    stat(strTempFileName,&st_filestat);

    // 判断是否是目录,如果是,处理各级子目录。
    if (S_ISDIR(st_filestat.st_mode))
    {
      if (bAndChild == true)
      {
        if (_OpenDir(strTempFileName,in_MatchStr,in_MaxCount,bAndChild) == false) 
        {
          closedir(dir); return false;
        }
      }
    }
    else
    {
      // 如果是文件,把能匹配上的文件放入m_vFileName容器中。
      if (MatchStr(st_fileinfo->d_name,in_MatchStr) == false) continue;

      m_vFileName.push_back(strTempFileName);

      if ( m_vFileName.size()>=in_MaxCount ) break;
    }
  }

  closedir(dir);

  return true;
}

/*
st_gid 
  Numeric identifier of group that owns file (UNIX-specific) This field will always be zero on NT systems. A redirected file is classified as an NT file.
st_atime
  Time of last access of file.
st_ctime
  Time of creation of file.
st_dev
  Drive number of the disk containing the file (same as st_rdev).
st_ino
  Number of the information node (the inode) for the file (UNIX-specific). On UNIX file systems, the inode describes the file date and time stamps, permissions, and content. When files are hard-linked to one another, they share the same inode. The inode, and therefore st_ino, has no meaning in the FAT, HPFS, or NTFS file systems.
st_mode
  Bit mask for file-mode information. The _S_IFDIR bit is set if path specifies a directory; the _S_IFREG bit is set if path specifies an ordinary file or a device. User read/write bits are set according to the file’s permission mode; user execute bits are set according to the filename extension.
st_mtime
  Time of last modification of file.
st_nlink
  Always 1 on non-NTFS file systems.
st_rdev
  Drive number of the disk containing the file (same as st_dev).
st_size
  Size of the file in bytes; a 64-bit integer for _stati64 and _wstati64
st_uid
  Numeric identifier of user who owns file (UNIX-specific). This field will always be zero on NT systems. A redirected file is classified as an NT file.
*/

// 从m_vFileName容器中获取一条记录(文件名),同时得到该文件的大小、修改时间等信息。
// 调用OpenDir方法时,m_vFileName容器被清空,m_pos归零,每调用一次ReadDir方法m_pos加1。
// 当m_pos小于m_vFileName.size(),返回true,否则返回false。
bool CDir::ReadDir()
{
  initdata();

  int ivsize=m_vFileName.size();

  // 如果已读完,清空容器
  if (m_pos >= ivsize) 
  {
    m_pos=0; m_vFileName.clear(); return false;
  }

  int pos=0;

  pos=m_vFileName[m_pos].find_last_of("/");

  // 目录名
  STRCPY(m_DirName,sizeof(m_DirName),m_vFileName[m_pos].substr(0,pos).c_str());

  // 文件名
  STRCPY(m_FileName,sizeof(m_FileName),m_vFileName[m_pos].substr(pos+1,m_vFileName[m_pos].size()-pos-1).c_str());

  // 文件全名,包括路径
  SNPRINTF(m_FullFileName,sizeof(m_FullFileName),300,"%s",m_vFileName[m_pos].c_str());

  struct stat st_filestat;

  stat(m_FullFileName,&st_filestat);

  m_FileSize=st_filestat.st_size;

  struct tm nowtimer;

  if (strcmp(m_DateFMT,"yyyy-mm-dd hh24:mi:ss") == 0)
  {
    nowtimer = *localtime(&st_filestat.st_mtime);
    // localtime_r(&st_filestat.st_mtime,&nowtimer); 
    nowtimer.tm_mon++;
    snprintf(m_ModifyTime,20,"%04u-%02u-%02u %02u:%02u:%02u",\
             nowtimer.tm_year+1900,nowtimer.tm_mon,nowtimer.tm_mday,\
             nowtimer.tm_hour,nowtimer.tm_min,nowtimer.tm_sec);

    nowtimer = *localtime(&st_filestat.st_ctime);
    // localtime_r(&st_filestat.st_ctime,&nowtimer); 
    nowtimer.tm_mon++;
    snprintf(m_CreateTime,20,"%04u-%02u-%02u %02u:%02u:%02u",\
             nowtimer.tm_year+1900,nowtimer.tm_mon,nowtimer.tm_mday,\
             nowtimer.tm_hour,nowtimer.tm_min,nowtimer.tm_sec);

    nowtimer = *localtime(&st_filestat.st_atime);
    // localtime_r(&st_filestat.st_atime,&nowtimer); 
    nowtimer.tm_mon++;
    snprintf(m_AccessTime,20,"%04u-%02u-%02u %02u:%02u:%02u",\
             nowtimer.tm_year+1900,nowtimer.tm_mon,nowtimer.tm_mday,\
             nowtimer.tm_hour,nowtimer.tm_min,nowtimer.tm_sec);
  }

  if (strcmp(m_DateFMT,"yyyymmddhh24miss") == 0)
  {
    nowtimer = *localtime(&st_filestat.st_mtime);
    // localtime_r(&st_filestat.st_mtime,&nowtimer); 
    nowtimer.tm_mon++;
    snprintf(m_ModifyTime,20,"%04u%02u%02u%02u%02u%02u",\
             nowtimer.tm_year+1900,nowtimer.tm_mon,nowtimer.tm_mday,\
             nowtimer.tm_hour,nowtimer.tm_min,nowtimer.tm_sec);

    nowtimer = *localtime(&st_filestat.st_ctime);
    // localtime_r(&st_filestat.st_ctime,&nowtimer); 
    nowtimer.tm_mon++;
    snprintf(m_CreateTime,20,"%04u%02u%02u%02u%02u%02u",\
             nowtimer.tm_year+1900,nowtimer.tm_mon,nowtimer.tm_mday,\
             nowtimer.tm_hour,nowtimer.tm_min,nowtimer.tm_sec);

    nowtimer = *localtime(&st_filestat.st_atime);
    // localtime_r(&st_filestat.st_atime,&nowtimer); 
    nowtimer.tm_mon++;
    snprintf(m_AccessTime,20,"%04u%02u%02u%02u%02u%02u",\
             nowtimer.tm_year+1900,nowtimer.tm_mon,nowtimer.tm_mday,\
             nowtimer.tm_hour,nowtimer.tm_min,nowtimer.tm_sec);
  }

  m_pos++;

  return true;
}

CDir::~CDir()
{
  m_vFileName.clear();

  // m_vDirName.clear();
}

// 删除目录中的文件,类似Linux系统的rm命令。
// filename:待删除的文件名,建议采用绝对路径的文件名,例如/tmp/root/data.xml。
// times:执行删除文件的次数,缺省是1,建议不要超过3,从实际应用的经验看来,如果删除文件第1次不成功,再尝试
// 2次是可以的,更多就意义不大了。还有,如果执行删除失败,usleep(100000)后再重试。
// 返回值:true-删除成功;false-删除失败,失败的主要原因是权限不足。
// 在应用开发中,可以用REMOVE函数代替remove库函数。
bool REMOVE(const char *filename,const int times)
{
  // 如果文件不存在,直接返回失败
  if (access(filename,R_OK) != 0) return false;

  for (int ii=0;ii<times;ii++)
  {
    if (remove(filename) == 0) return true;

    usleep(100000);
  }

  return false;
}

// 把文件重命名,类似Linux系统的mv命令。
// srcfilename:原文件名,建议采用绝对路径的文件名。
// dstfilename:目标文件名,建议采用绝对路径的文件名。
// times:执行重命名文件的次数,缺省是1,建议不要超过3,从实际应用的经验看来,如果重命名文件第1次不成功,再尝
// 试2次是可以的,更多次就意义不大了。还有,如果执行重命名失败,usleep(100000)后再重试。
// 返回值:true-重命名成功;false-重命名失败,失败的主要原因是权限不足或磁盘空间不够,如果原文件和目标文件不
// 在同一个磁盘分区,重命名也可能失败。
// 注意,在重命名文件之前,会自动创建dstfilename参数中的目录名。
// 在应用开发中,可以用RENAME函数代替rename库函数。
bool RENAME(const char *srcfilename,const char *dstfilename,const int times)
{
  // 如果文件不存在,直接返回失败
  if (access(srcfilename,R_OK) != 0) return false;

  if (MKDIR(dstfilename) == false) return false;

  for (int ii=0;ii<times;ii++)
  {
    if (rename(srcfilename,dstfilename) == 0) return true;

    usleep(100000);
  }

  return false;
}


CTcpClient::CTcpClient()
{
  m_connfd=-1;
  memset(m_ip,0,sizeof(m_ip));
  m_port=0;
  m_btimeout=false;
}

bool CTcpClient::ConnectToServer(const char *ip,const int port)
{
  // 如果已连接到服务端,则断开,这种处理方法没有特别的原因,不要纠结。
  if (m_connfd!=-1) { close(m_connfd); m_connfd=-1; }
 
  // 忽略SIGPIPE信号,防止程序异常退出。
  // 如果send到一个disconnected socket上,内核就会发出SIGPIPE信号。这个信号
  // 的缺省处理方法是终止进程,大多数时候这都不是我们期望的。我们重新定义这
  // 个信号的处理方法,大多数情况是直接屏蔽它。
  signal(SIGPIPE,SIG_IGN);   

  STRCPY(m_ip,sizeof(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)) )
  {
    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;
}

// 接收服务端发送过来的数据。
// buffer:接收数据缓冲区的地址,数据的长度存放在m_buflen成员变量中。
// itimeout:等待数据的超时时间,单位:秒,缺省值是0-无限等待。
// 返回值:true-成功;false-失败,失败有两种情况:1)等待超时,成员变量m_btimeout的值被设置为true;2)socket连接已不可用。
bool CTcpClient::Read(char *buffer,const int itimeout)
{
  if (m_connfd==-1) return false;

  // 如果itimeout>0,表示需要等待itimeout秒,如果itimeout秒后还没有数据到达,返回false。
  if (itimeout>0)
  {
    struct pollfd fds;
    fds.fd=m_connfd;
    fds.events=POLLIN;  
    int iret;
    m_btimeout=false;
    if ( (iret=poll(&fds,1,itimeout*1000)) <= 0 )
    {
      if (iret==0) m_btimeout = true;
      return false;
    }
  }

  m_buflen = 0;
  return (TcpRead(m_connfd,buffer,&m_buflen));
}

bool CTcpClient::Write(const char *buffer,const int ibuflen)
{
  if (m_connfd==-1) return false;

  int ilen=ibuflen;

  if (ibuflen==0) ilen=strlen(buffer);

  return(TcpWrite(m_connfd,buffer,ilen));
}

void CTcpClient::Close()
{
  if (m_connfd > 0) close(m_connfd); 

  m_connfd=-1;
  memset(m_ip,0,sizeof(m_ip));
  m_port=0;
  m_btimeout=false;
}

CTcpClient::~CTcpClient()
{
  Close();
}

CTcpServer::CTcpServer()
{
  m_listenfd=-1;
  m_connfd=-1;
  m_socklen=0;
  m_btimeout=false;
}

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信号,防止程序异常退出。
  signal(SIGPIPE,SIG_IGN);   

  // 打开SO_REUSEADDR选项,当服务端连接处于TIME_WAIT状态时可以再次启动服务器,
  // 否则bind()可能会不成功,报:Address already in use。
  //char opt = 1; unsigned int len = sizeof(opt);
  int opt = 1; unsigned int len = sizeof(opt);
  setsockopt(m_listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,len);    

  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;

  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(char *buffer,const int itimeout)
{
  if (m_connfd==-1) return false;

  // 如果itimeout>0,表示需要等待itimeout秒,如果itimeout秒后还没有数据到达,返回false。
  if (itimeout>0)
  {
    struct pollfd fds;
    fds.fd=m_connfd;
    fds.events=POLLIN;
    m_btimeout=false;
    int iret;
    if ( (iret=poll(&fds,1,itimeout*1000)) <= 0 )
    {
      if (iret==0) m_btimeout = true;
      return false;
    }
  }

  m_buflen = 0;
  return(TcpRead(m_connfd,buffer,&m_buflen));
}

bool CTcpServer::Write(const char *buffer,const int ibuflen)
{
  if (m_connfd==-1) return false;

  int ilen = ibuflen;
  if (ilen==0) ilen=strlen(buffer);

  return(TcpWrite(m_connfd,buffer,ilen));
}

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();
}

// 接收socket的对端发送过来的数据。
// sockfd:可用的socket连接。
// buffer:接收数据缓冲区的地址。
// ibuflen:本次成功接收数据的字节数。
// itimeout:接收等待超时的时间,单位:秒,-1-不等待;0-无限等待;>0-等待的秒数。
// 返回值:true-成功;false-失败,失败有两种情况:1)等待超时;2)socket连接已不可用。
bool TcpRead(const int sockfd,char *buffer,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;
  }

  (*ibuflen) = 0;  // 报文长度变量初始化为0。

  // 先读取报文长度,4个字节。
  if (Readn(sockfd,(char*)ibuflen,4) == false) return false;

  (*ibuflen)=ntohl(*ibuflen);  // 把报文长度由网络字节序转换为主机字节序。

  // 再读取报文内容。
  if (Readn(sockfd,buffer,(*ibuflen)) == false) return false;

  return true;
}

// 向socket的对端发送数据。
// sockfd:可用的socket连接。
// buffer:待发送数据缓冲区的地址。
// ibuflen:待发送数据的字节数,如果发送的是ascii字符串,ibuflen填0或字符串的长度,
//          如果是二进制流数据,ibuflen为二进制数据块的大小。
// 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
bool TcpWrite(const int sockfd,const char *buffer,const int ibuflen)
{
  if (sockfd==-1) return false;

  int ilen=0;  // 报文长度。

  // 如果ibuflen==0,就认为需要发送的是字符串,报文长度为字符串的长度。
  if (ibuflen==0) ilen=strlen(buffer);
  else ilen=ibuflen;

  int ilenn=htonl(ilen);    // 把报文长度转换为网络字节序。

  char TBuffer[ilen+4];     // 发送缓冲区。
  memset(TBuffer,0,sizeof(TBuffer));  // 清区发送缓冲区。
  memcpy(TBuffer,&ilenn,4);           // 把报文长度拷贝到缓冲区。
  memcpy(TBuffer+4,buffer,ilen);      // 把报文内容拷贝到缓冲区。
  
  // 发送缓冲区中的数据。
  if (Writen(sockfd,TBuffer,ilen+4) == 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;
}


// 复制文件,类似Linux系统的cp命令。
// srcfilename:原文件名,建议采用绝对路径的文件名。
// dstfilename:目标文件名,建议采用绝对路径的文件名。
// 返回值:true-复制成功;false-复制失败,失败的主要原因是权限不足或磁盘空间不够。
// 注意:
// 1)在复制文件之前,会自动创建dstfilename参数中的目录名。
// 2)复制文件的过程中,采用临时文件命名的方法,复制完成后再改名为dstfilename,避免中间状态的文件被读取。
// 3)复制后的文件的时间与原文件相同,这一点与Linux系统cp命令不同。
bool COPY(const char *srcfilename,const char *dstfilename)
{
  if (MKDIR(dstfilename) == false) return false;

  char strdstfilenametmp[301];
  SNPRINTF(strdstfilenametmp,sizeof(strdstfilenametmp),300,"%s.tmp",dstfilename);

  int  srcfd,dstfd;

  srcfd=dstfd=-1;

  int iFileSize=FileSize(srcfilename);

  int  bytes=0;
  int  total_bytes=0;
  int  onread=0;
  char buffer[5000];

  if ( (srcfd=open(srcfilename,O_RDONLY)) < 0 ) return false;

  if ( (dstfd=open(strdstfilenametmp,O_WRONLY|O_CREAT|O_TRUNC,S_IWUSR|S_IRUSR|S_IXUSR)) < 0) { close(srcfd); return false; }

  while (true)
  {
    memset(buffer,0,sizeof(buffer));

    if ((iFileSize-total_bytes) > 5000) onread=5000;
    else onread=iFileSize-total_bytes;

    bytes=read(srcfd,buffer,onread);

    if (bytes > 0) write(dstfd,buffer,bytes);

    total_bytes = total_bytes + bytes;

    if (total_bytes == iFileSize) break;
  }

  close(srcfd);

  close(dstfd);

  // 更改文件的修改时间属性
  char strmtime[21];
  memset(strmtime,0,sizeof(strmtime));
  FileMTime(srcfilename,strmtime);
  UTime(strdstfilenametmp,strmtime);

  if (RENAME(strdstfilenametmp,dstfilename) == false) { REMOVE(strdstfilenametmp); return false; }

  return true;
}


CTimer::CTimer()
{
  memset(&m_start,0,sizeof(struct timeval));
  memset(&m_end,0,sizeof(struct timeval));

  // 开始计时
  Start();
}

// 开始计时
void CTimer::Start()
{
  gettimeofday( &m_start, 0 );
}

// 计算已逝去的时间,单位:秒,小数点后面是微秒
// 每调用一次本方法之后,自动调用Start方法重新开始计时。
double CTimer::Elapsed()
{

  gettimeofday( &m_end, 0 );

  double dstart,dend;

  dstart=dend=0;

  char strtemp[51];
  SNPRINTF(strtemp,sizeof(strtemp),30,"%ld.%06ld",m_start.tv_sec,m_start.tv_usec);
  dstart=atof(strtemp);

  SNPRINTF(strtemp,sizeof(strtemp),30,"%ld.%06ld",m_end.tv_sec,m_end.tv_usec);
  dend=atof(strtemp);

  // 重新开始计时
  Start();

  return dend-dstart;
}

CSEM::CSEM()
{
  m_semid=-1;
  m_sem_flg=SEM_UNDO;
}

// 如果信号量已存在,获取信号量;如果信号量不存在,则创建它并初始化为value。
bool CSEM::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)获取信号量,如果成功,函数返回。
  // 2)如果失败,则创建信号量。
  // 3) 设置信号量的初始值。

  // 获取信号量。
  if ( (m_semid=semget(key,1,0666)) == -1)
  {
    // 如果信号量不存在,创建它。
    if (errno==2)
    {
      // 用IPC_EXCL标志确保只有一个进程创建并初始化信号量,其它进程只能获取。
      if ( (m_semid=semget(key,1,0666|IPC_CREAT|IPC_EXCL)) == -1)
      {
        if (errno!=EEXIST)
        {
          perror("init 1 semget()"); return false;
        }
        if ( (m_semid=semget(key,1,0666)) == -1)
        { perror("init 2 semget()"); return false; }
    
        return true;
      }

      // 信号量创建成功后,还需要把它初始化成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;
}

bool CSEM::P(short sem_op)
{
  if (m_semid==-1) return false;

  struct sembuf sem_b;
  sem_b.sem_num = 0;      // 信号量编号,0代表第一个信号量。
  sem_b.sem_op = sem_op;  // P操作的sem_op必须小于0。
  sem_b.sem_flg = m_sem_flg;   
  if (semop(m_semid,&sem_b,1) == -1) { perror("p semop()"); return false; }

  return true;
}

bool CSEM::V(short sem_op)
{
  if (m_semid==-1) return false;

  struct sembuf sem_b;
  sem_b.sem_num = 0;      // 信号量编号,0代表第一个信号量。
  sem_b.sem_op = sem_op;  // V操作的sem_op必须大于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 CSEM::value()
{
  return semctl(m_semid,0,GETVAL);
}

bool CSEM::destroy()
{
  if (m_semid==-1) return false;

  if (semctl(m_semid,0,IPC_RMID) == -1) { perror("destroy semctl()"); return false; }

  return true;
}

CSEM::~CSEM()
{
}

CPActive::CPActive()
{
  m_shmid=0;
  m_pos=-1;
  m_shm=0;
}

// 把当前进程的心跳信息加入共享内存进程组中。
bool CPActive::AddPInfo(const int timeout,const char *pname,CLogFile *logfile)
{
  if (m_pos!=-1) return true;

  if (m_sem.init(SEMKEYP) == false)  // 初始化信号量。
  {
    if (logfile!=0) logfile->Write("创建/获取信号量(%x)失败。\n",SEMKEYP); 
    else printf("创建/获取信号量(%x)失败。\n",SEMKEYP);

    return false;
  }

  // 创建/获取共享内存,键值为SHMKEYP,大小为MAXNUMP个st_procinfo结构体的大小。
  if ( (m_shmid = shmget((key_t)SHMKEYP, MAXNUMP*sizeof(struct st_procinfo), 0666|IPC_CREAT)) == -1)
  { 
    if (logfile!=0) 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,sizeof(stprocinfo.pname),pname,50); // 进程名。

  // 进程id是循环使用的,如果曾经有一个进程异常退出,没有清理自己的心跳信息,
  // 它的进程信息将残留在共享内存中,不巧的是,当前进程重用了上述进程的id,
  // 这样就会在共享内存中存在两个进程id相同的记录,守护进程检查到残留进程的
  // 心跳时,会向进程id发送退出信号,这个信号将误杀当前进程。

  // 如果共享内存中存在当前进程编号,一定是其它进程残留的数据,当前进程就重用该位置。
  for (int ii=0;ii<MAXNUMP;ii++)
  {
    if ( (m_shm+ii)->pid==stprocinfo.pid ) { m_pos=ii; break; }
  }

  m_sem.P();  // 给共享内存上锁。

  if (m_pos==-1)
  {
    // 如果m_pos==-1,共享内存的进程组中不存在当前进程编号,找一个空位置。
    for (int ii=0;ii<MAXNUMP;ii++)
      if ( (m_shm+ii)->pid==0 ) { m_pos=ii; break; }
  }

  if (m_pos==-1) 
  { 
    if (logfile!=0) logfile->Write("共享内存空间已用完。\n");
    else printf("共享内存空间已用完。\n");

    m_sem.V();  // 解锁。

    return false; 
  }

  // 把当前进程的心跳信息存入共享内存的进程组中。
  memcpy(m_shm+m_pos,&stprocinfo,sizeof(struct st_procinfo)); 

  m_sem.V();   // 解锁。

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