This commit is contained in:
eviwbh 2024-09-12 18:53:27 +08:00
commit 41f439c7f4
11 changed files with 5313 additions and 0 deletions

62
EL/README.md Normal file
View File

@ -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 TT, int MaxLength>
class squeue
socket通讯的服务端类
class ctcpserver
socket通讯的客户端类
class ctcpclient
```

58
EL/_cmel.h Normal file
View File

@ -0,0 +1,58 @@
/****************************************************************************************
* _cmel.h
*****************************************************************************************/
#ifndef _cmel_H
#define _cmel_H 1
#include <stdio.h>
#include <utime.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include <time.h>
#include <math.h>
#include <stdarg.h>
#include <errno.h>
#include <signal.h>
#include <netdb.h>
#include <locale.h>
#include <dirent.h>
#include <termios.h>
#include <pthread.h>
#include <poll.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/timerfd.h>
#include <sys/signalfd.h>
#include <atomic>
#include <iostream>
#include <fstream>
#include <string>
#include <list>
#include <map>
#include <unordered_map>
#include <forward_list>
#include <vector>
#include <deque>
#include <memory>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <algorithm>
#include <thread>
#endif

1828
EL/_el.cpp Normal file

File diff suppressed because it is too large Load Diff

824
EL/_el.h Normal file
View File

@ -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"。
// 注意1str参数不需要支持"*"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<string> 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待拆分的字符串。
// sepstrbuffer中采用的分隔符注意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格式的字符串的内容如下
// <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 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+1windows平台是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+1windows平台是len。
return str;
}
///////////////////////////////////// /////////////////////////////////////
// 时间操作的若干函数。
/*
strtime
timetvl03030-3030
fmtfmt每部分的含义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"
1hh24hh
2timetostr()
3fmt与上述格式都匹配strtime的内容将为空
40
*/
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权限不足2pathorfilename参数不是合法的文件名或目录名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<string> 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<typename T>
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<typename T>
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等待超时2socket连接已不可用。
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等待超时2socket连接已不可用。
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等待超时2socket连接已不可用。
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字节的数据后返回truesocket连接不可用返回false。
bool readn(const int sockfd,char *buffer,const size_t n);
// 向已经准备好的socket中写入数据。
// sockfd已经准备好的socket连接。
// buffer待写入数据的地址。
// n待写入数据的字节数。
// 返回值成功写入完n字节的数据后返回truesocket连接不可用返回false。
bool writen(const int sockfd,const char *buffer,const size_t n);
// 以上是socket通讯的函数和类
///////////////////////////////////// /////////////////////////////////////
// 忽略关闭全部的信号、关闭全部的IO缺省只忽略信号不关IO。
void closeioandsignal(bool bcloseio=false);
// 循环队列。
template <class TT, int MaxLength>
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填1sem_flg填SEM_UNDO。
// 如果用于生产消费者模型value填0sem_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

232
EL/_ftp.cpp Normal file
View File

@ -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

116
EL/_ftp.h Normal file
View File

@ -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服务器。
// hostftp服务器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服务器的当前工作目录。
// remotedirftp服务器上的目录名。
// 返回值true-成功false-失败。
bool chdir(const string &remotedir);
// 在ftp服务器上创建目录。
// remotedirftp服务器上待创建的目录名。
// 返回值true-成功false-失败。
bool mkdir(const string &remotedir);
// 删除ftp服务器上的目录。
// remotedirftp服务器上待删除的目录名。
// 返回值true-成功如果权限不足、目录不存在或目录不为空会返回false。
bool rmdir(const string &remotedir);
// 发送NLST命令列出ftp服务器目录中的子目录名和文件名。
// remotedirftp服务器的目录名。
// 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服务器上的文件。
// srcremotefilenameftp服务器上的原文件名。
// dstremotefilenameftp服务器上的目标文件名。
// 返回值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

1455
EL/ftplib.c Normal file

File diff suppressed because it is too large Load Diff

120
EL/ftplib.h Normal file
View File

@ -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 <limits.h>
#include <inttypes.h>
/* 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 */

16
EL/makefile Normal file
View File

@ -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

4
README.md Normal file
View File

@ -0,0 +1,4 @@
# 面试专用贴
数据访问接口demoC++ webserver http协议处理

598
webserver.cpp Normal file
View File

@ -0,0 +1,598 @@
/*
* webserver.cpp访
*/
#include "_el.h"
#include "_ooci.h"
using namespace eviwbh;
void EXIT(int sig); // 进程退出函数。
clogfile logfile; // 本程序运行的日志。
// 初始化服务端的监听端口。
int initserver(const int port);
// 从GET请求中获取参数的值strget-GET请求报文的内容name-参数名value-参数值。
bool getvalue(const string &strget,const string &name,string &value);
struct st_client // 客户端的结构体。
{
string clientip; // 客户端的ip地址。
int clientatime=0; // 客户端最后一次活动的时间。
string recvbuffer; // 客户端的接收缓冲区。
string sendbuffer; // 客户端的发送缓冲区。
};
// 接收/发送队列的结构体。
struct st_recvmesg
{
int sock=0; // 客户端的socket。
string message; // 接收/发送的报文。
st_recvmesg(int in_sock,string &in_message):sock(in_sock),message(in_message){ logfile.write("构造了报文。\n");}
};
class AA // 线程类。
{
private:
queue<shared_ptr<st_recvmesg>> m_rq; // 接收队列底层容器用deque。
mutex m_mutex_rq; // 接收队列的互斥锁。
condition_variable m_cond_rq; // 接收队列的条件变量。
queue<shared_ptr<st_recvmesg>> m_sq; // 发送队列底层容器用deque。
mutex m_mutex_sq; // 发送队列的互斥锁。
int m_sendpipe[2] = {0}; // 工作线程通知发送线程的无名管道。
unordered_map<int,struct st_client> clientmap; // 存放客户端对象的哈希表,俗称状态机。
atomic_bool m_exit; // 如果m_exit==true工作线程和发送线程将退出。
public:
int m_recvpipe[2] = {0}; // 主进程通知接收线程退出的管道。主进程要用到该成员所以声明为public。
AA()
{
pipe(m_sendpipe); // 创建工作线程通知发送线程的无名管道。
pipe(m_recvpipe); // 创建主进程通知接收线程退出的管道。
m_exit==false;
}
// 接收线程主函数listenport-监听端口。
void recvfunc(int listenport)
{
// 初始化服务端用于监听的socket。
int listensock=initserver(listenport);
if (listensock<0)
{
logfile.write("接收线程initserver(%d) failed.\n",listenport); return;
}
// 创建epoll句柄。
int epollfd=epoll_create1(0);
// 为监听的socket准备读事件。
struct epoll_event ev; // 声明事件的数据结构。
ev.events=EPOLLIN; // 读事件。
ev.data.fd=listensock; // 指定事件的自定义数据会随着epoll_wait()返回的事件一并返回。
epoll_ctl(epollfd,EPOLL_CTL_ADD,listensock,&ev); // 把监听的socket的事件加入epollfd中。
// 把接收主进程通知的管道加入epoll。
ev.data.fd = m_recvpipe[0];
ev.events = EPOLLIN;
epoll_ctl(epollfd,EPOLL_CTL_ADD,ev.data.fd,&ev);
struct epoll_event evs[10]; // 存放epoll返回的事件。
while (true) // 进入事件循环。
{
// 等待监视的socket有事件发生。
int infds=epoll_wait(epollfd,evs,10,-1);
// 返回失败。
if (infds < 0) { logfile.write("接收线程epoll() failed。\n"); return; }
// 遍历epoll返回的已发生事件的数组evs。
for (int ii=0;ii<infds;ii++)
{
logfile.write("接收线程已发生事件的fd=%d(%d)\n",evs[ii].data.fd,evs[ii].events);
////////////////////////////////////////////////////////
// 如果发生事件的是listensock表示有新的客户端连上来。
if (evs[ii].data.fd==listensock)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int clientsock = accept(listensock,(struct sockaddr*)&client,&len);
fcntl(clientsock,F_SETFL,fcntl(clientsock,F_GETFD,0)|O_NONBLOCK); // 把socket设置为非阻塞。
logfile.write("接收线程accept client(socket=%d) ok.\n",clientsock);
clientmap[clientsock].clientip=inet_ntoa(client.sin_addr); // 保存客户端的ip地址。
clientmap[clientsock].clientatime=time(0); // 客户端的活动时间。
// 为新的客户端连接准备读事件并添加到epoll中。
ev.data.fd=clientsock;
ev.events=EPOLLIN;
epoll_ctl(epollfd,EPOLL_CTL_ADD,clientsock,&ev);
continue;
}
// 如果是管道有读事件。
if (evs[ii].data.fd==m_recvpipe[0])
{
logfile.write("接收线程:即将退出。\n");
m_exit=true; // 把退出的原子变量置为true。
m_cond_rq.notify_all(); // 通知全部的工作线程退出。
write(m_sendpipe[1],(char*)"o",1); // 通知发送线程退出。
return;
}
// 如果是客户端连接的socke有事件分两种情况1客户端有报文发过来2客户端连接已断开。
if (evs[ii].events&EPOLLIN) // 判断是否为读事件。
{
char buffer[5000]; // 存放从接收缓冲区中读取的数据。
int buflen=0; // 从接收缓冲区中读取的数据的大小。
// 读取客户端的请求报文。
if ( (buflen=recv(evs[ii].data.fd,buffer,sizeof(buffer),0)) <= 0 )
{
// 如果连接已断开。
logfile.write("接收线程client(%d) disconnected。\n",evs[ii].data.fd);
close(evs[ii].data.fd); // 关闭客户端的连接。
clientmap.erase(evs[ii].data.fd); // 从状态机中删除客户端。
continue;
}
// 以下是成功读取了客户端数据的流程。
logfile.write("接收线程recv %d,%d bytes\n",evs[ii].data.fd,buflen);
// 把读取到的数据追加到socket的recvbuffer中。
clientmap[evs[ii].data.fd].recvbuffer.append(buffer,buflen);
// 如果recvbuffer中的内容以"\r\n\r\n"结束表示已经是一个完整的http请求报文了。
if ( clientmap[evs[ii].data.fd].recvbuffer.compare( clientmap[evs[ii].data.fd].recvbuffer.length()-4,4,"\r\n\r\n")==0)
{
logfile.write("接收线程:接收到了一个完整的请求报文。\n");
inrq((int)evs[ii].data.fd, clientmap[evs[ii].data.fd].recvbuffer); // 把完整的请求报文入队,交给工作线程。
clientmap[evs[ii].data.fd].recvbuffer.clear(); // 清空socket的recvbuffer。
}
else
{
if (clientmap[evs[ii].data.fd].recvbuffer.size()>1000)
{
close(evs[ii].data.fd); // 关闭客户端的连接。
clientmap.erase(evs[ii].data.fd); // 从状态机中删除客户端。
// 可以考虑增加把客户端的ip加入黑名单。
}
}
clientmap[evs[ii].data.fd].clientatime=time(0); // 更新客户端的活动时间
}
}
}
}
// 把客户端的socket和请求报文放入接收队列sock-客户端的socketmessage-客户端的请求报文。
void inrq(int sock,string &message)
{
shared_ptr<st_recvmesg> ptr=make_shared<st_recvmesg>(sock,message); // 创建接收报文对象。
lock_guard<mutex> lock(m_mutex_rq); // 申请加锁。
m_rq.push(ptr); // 把接收报文对象扔到接收队列中。
m_cond_rq.notify_one(); // 通知工作线程处理接收队列中的报文。
}
// 工作线程主函数处理接收队列中的请求报文id-线程编号(仅用于调试和日志,没什么其它的含义)。
void workfunc(int id)
{
connection conn;
if (conn.connecttodb("idc/idcpwd@snorcl11g_128","Simplified Chinese_China.AL32UTF8")!=0)
{
logfile.write("connect database(idc/idcpwd@snorcl11g_128) failed.\n%s\n",conn.message()); return;
}
while (true)
{
shared_ptr<st_recvmesg> ptr;
{
unique_lock<mutex> lock(m_mutex_rq); // 把互斥锁转换成unique_lock<mutex>,并申请加锁。
while (m_rq.empty()) // 如果队列空进入循环否则直接处理数据。必须用循环不能用if
{
m_cond_rq.wait(lock); // 等待生产者的唤醒信号。
if (m_exit==true)
{
logfile.write("工作线程(%d即将退出。\n",id); return;
}
}
ptr=m_rq.front(); m_rq.pop(); // 出队一个元素。
}
// 处理出队的元素,即客户端的请求报文。
logfile.write("工作线程(%d请求sock=%d,mesg=%s\n",id,ptr->sock,ptr->message.c_str());
/////////////////////////////////////////////////////////////
// 在这里增加处理客户端请求报文的代码解析请求报文、判断权限、执行查询数据的SQL语句、生成响应报文
string sendbuf;
bizmain(conn,ptr->message,sendbuf);
string message=sformat(
"HTTP/1.1 200 OK\r\n"
"Server: webserver\r\n"
"Content-Type: text/html;charset=utf-8\r\n")+sformat("Content-Length:%d\r\n\r\n",sendbuf.size())+sendbuf;
/////////////////////////////////////////////////////////////
logfile.write("工作线程(%d回应sock=%d,mesg=%s\n",id,ptr->sock,message.c_str());
// 把客户端的socket和响应报文放入发送队列。
insq(ptr->sock,message);
}
}
// 处理客户端的请求报文,生成响应报文。
// conn-数据库连接recvbuf-http请求报文sendbuf-http响应报文的数据部分不包括状态行和头部信息。
void bizmain(connection &conn,const string & recvbuf,string & sendbuf)
{
string username,passwd,intername;
getvalue(recvbuf,"username",username); // 解析用户名。
getvalue(recvbuf,"passwd",passwd); // 解析密码。
getvalue(recvbuf,"intername",intername); // 解析接口名。
// 1验证用户名和密码是否正确。
sqlstatement stmt(&conn);
stmt.prepare("select ip from T_USERINFO where username=:1 and passwd=:2 and rsts=1");
string ip;
stmt.bindin(1,username);
stmt.bindin(2,passwd);
stmt.bindout(1,ip,50);
stmt.execute();
if (stmt.next()!=0)
{
sendbuf="<retcode>-1</retcode><message>用户名或密码不正确。</message>"; return;
}
// 2判断客户连上来的地址是否在绑定ip地址的列表中。
if (ip.empty()==false)
{
// 略去十万八千行代码。
}
// 3判断用户是否有访问接口的权限。
stmt.prepare("select count(*) from T_USERANDINTER "
"where username=:1 and intername=:2 and intername in (select intername from T_INTERCFG where rsts=1)");
stmt.bindin(1,username);
stmt.bindin(2,intername);
int icount=0;
stmt.bindout(1,icount);
stmt.execute();
stmt.next();
if (icount==0)
{
sendbuf="<retcode>-1</retcode><message>用户无权限,或接口不存在。</message>"; return;
}
// 4根据接口名获取接口的配置参数。
// 从接口参数配置表T_INTERCFG中加载接口参数。
string selectsql,colstr,bindin;
stmt.prepare("select selectsql,colstr,bindin from T_INTERCFG where intername=:1");
stmt.bindin(1,intername); // 接口名。
stmt.bindout(1,selectsql,1000); // 接口SQL。
stmt.bindout(2,colstr,300); // 输出列名。
stmt.bindout(3,bindin,300); // 接口参数。
stmt.execute(); // 这里基本上不用判断返回值,出错的可能几乎没有。
if (stmt.next()!=0)
{
sendbuf="<retcode>-1</retcode><message>内部错误。</message>"; return;
}
// http://192.168.174.132:8080?username=ty&passwd=typwd&intername=getzhobtmind3&
// obtid=59287&begintime=20211024094318&endtime=20211024113920
// SQL语句 select obtid,to_char(ddatetime,'yyyymmddhh24miss'),t,p,u,wd,wf,r,vis from T_ZHOBTMIND
// where obtid=:1 and ddatetime>=to_date(:2,'yyyymmddhh24miss') and ddatetime<=to_date(:3,'yyyymmddhh24miss')
// colstr字段 obtid,ddatetime,t,p,u,wd,wf,r,vis
// bindin字段obtid,begintime,endtime
// 5准备查询数据的SQL语句。
stmt.prepare(selectsql);
//////////////////////////////////////////////////
// 根据接口配置中的参数列表bindin字段从请求报文中解析出参数的值绑定到查询数据的SQL语句中。
// 拆分输入参数bindin。
ccmdstr cmdstr;
cmdstr.splittocmd(bindin,",");
// 声明用于存放输入参数的数组。
vector<string> invalue;
invalue.resize(cmdstr.size());
// 从http的GET请求报文中解析出输入参数绑定到sql中。
for (int ii=0;ii<cmdstr.size();ii++)
{
getvalue(recvbuf,cmdstr[ii].c_str(),invalue[ii]);
stmt.bindin(ii+1,invalue[ii]);
}
//////////////////////////////////////////////////
//////////////////////////////////////////////////
// 绑定查询数据的SQL语句的输出变量。
// 拆分colstr可以得到结果集的字段数。
cmdstr.splittocmd(colstr,",");
// 用于存放结果集的数组。
vector<string> colvalue;
colvalue.resize(cmdstr.size());
// 把结果集绑定到colvalue数组。
for (int ii=0;ii<cmdstr.size();ii++)
stmt.bindout(ii+1,colvalue[ii]);
//////////////////////////////////////////////////
if (stmt.execute() != 0)
{
logfile.write("stmt.execute() failed.\n%s\n%s\n",stmt.sql(),stmt.message());
sformat(sendbuf,"<retcode>%d</retcode><message>%s</message>\n",stmt.rc(),stmt.message());
return;
}
sendbuf="<retcode>0</retcode><message>ok</message>\n";
sendbuf=sendbuf+"<data>\n"; // xml内容开始的标签<data>。
//////////////////////////////////////////////////
// 获取结果集每获取一条记录拼接xml。
while (true)
{
if (stmt.next() != 0) break; // 从结果集中取一条记录。
// 拼接每个字段的xml。
for (int ii=0;ii<cmdstr.size();ii++)
sendbuf=sendbuf+sformat("<%s>%s</%s>",cmdstr[ii].c_str(),colvalue[ii].c_str(),cmdstr[ii].c_str());
sendbuf=sendbuf+"<endl/>\n"; // 每行结束的标志。
}
//////////////////////////////////////////////////
sendbuf=sendbuf+"</data>\n"; // xml内容结尾的标签</data>。
logfile.write("intername=%s,count=%d\n",intername.c_str(),stmt.rpc());
// 写接口调用日志表T_USERLOG略去十万八千行代码。
}
// 把客户端的socket和响应报文放入发送队列sock-客户端的socketmessage-客户端的响应报文。
void insq(int sock,string &message)
{
{
shared_ptr<st_recvmesg> ptr=make_shared<st_recvmesg>(sock,message);
lock_guard<mutex> lock(m_mutex_sq); // 申请加锁。
m_sq.push(ptr);
}
write(m_sendpipe[1],(char*)"o",1); // 通知发送线程处理发送队列中的数据。
}
// 发送线程主函数,把发送队列中的数据发送给客户端。
void sendfunc()
{
// 创建epoll句柄。
int epollfd=epoll_create1(0);
struct epoll_event ev; // 声明事件的数据结构。
// 1信号epoll可以监视信号工作线程用信号通知发送线程。
// 2tcp工作线程与发送线程创建一个tcp连接工作线程用这个连接通知发送线程。
// 3管道管道也是fd工作线程用通知发送线程。
// 把发送队列的管道加入epoll。
ev.data.fd = m_sendpipe[0];
ev.events = EPOLLIN;
epoll_ctl(epollfd,EPOLL_CTL_ADD,ev.data.fd,&ev);
struct epoll_event evs[10]; // 存放epoll返回的事件。
while (true) // 进入事件循环。
{
// 等待监视的socket有事件发生。
int infds=epoll_wait(epollfd,evs,10,-1);
// 返回失败。
if (infds < 0) { logfile.write("发送线程epoll() failed。\n"); return; }
// 遍历epoll返回的已发生事件的数组evs。
for (int ii=0;ii<infds;ii++)
{
logfile.write("发送线程已发生事件的fd=%d(%d)\n",evs[ii].data.fd,evs[ii].events);
////////////////////////////////////////////////////////
// 如果发生事件的是管道,表示发送队列中有报文需要发送。
if (evs[ii].data.fd==m_sendpipe[0])
{
if (m_exit==true)
{
logfile.write("发送线程:即将退出。\n"); return;
}
char cc;
read(m_sendpipe[0], &cc, 1); // 读取管道中的数据,只有一个字符,不关心其内容。
shared_ptr<st_recvmesg> ptr;
lock_guard<mutex> lock(m_mutex_sq); // 申请加锁。
while (m_sq.empty()==false)
{
ptr=m_sq.front(); m_sq.pop(); // 出队一个元素(报文)。
// 把出队的报文保存到socket的发送缓冲区中。
clientmap[ptr->sock].sendbuffer.append(ptr->message);
// 关注客户端socket的写事件。
ev.data.fd=ptr->sock;
ev.events=EPOLLOUT;
epoll_ctl(epollfd,EPOLL_CTL_ADD,ev.data.fd,&ev);
}
continue;
}
////////////////////////////////////////////////////////
////////////////////////////////////////////////////////
// 判断客户端的socket是否有写事件发送缓冲区没有满
if (evs[ii].events&EPOLLOUT)
{
// 把响应报文发送给客户端。
int writen=send(evs[ii].data.fd,clientmap[evs[ii].data.fd].sendbuffer.data(),clientmap[evs[ii].data.fd].sendbuffer.length(),0);
logfile.write("发送线程:向%d发送了%d字节。\n",evs[ii].data.fd,writen);
// 删除socket缓冲区中已成功发送的数据。
clientmap[evs[ii].data.fd].sendbuffer.erase(0,writen);
// 如果socket缓冲区中没有数据了不再关心socket的写件事。
if (clientmap[evs[ii].data.fd].sendbuffer.length()==0)
{
ev.data.fd=evs[ii].data.fd;
epoll_ctl(epollfd,EPOLL_CTL_DEL,ev.data.fd,&ev);
}
}
////////////////////////////////////////////////////////
}
}
}
};
AA aa;
int main(int argc,char *argv[])
{
if (argc != 3)
{
printf("\n");
printf("Using :./webserver logfile port\n\n");
printf("Sample:./webserver /log/idc/webserver.log 5088\n\n");
printf(" /project/tools/bin/procctl 5 /project/tools/bin/webserver /log/idc/webserver.log 5088\n\n");
printf("基于HTTP协议的数据访问接口模块。\n");
printf("logfile 本程序运行的日是志文件。\n");
printf("port 服务端口例如80、8080。\n");
return -1;
}
// 关闭全部的信号和输入输出。
// 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程。
// 但请不要用 "kill -9 +进程号" 强行终止。
closeioandsignal(); signal(SIGINT,EXIT); signal(SIGTERM,EXIT);
// 打开日志文件。
if (logfile.open(argv[1])==false)
{
printf("打开日志文件失败(%s\n",argv[1]); return -1;
}
thread t1(&AA::recvfunc, &aa,atoi(argv[2])); // 创建接收线程。
thread t2(&AA::workfunc, &aa,1); // 创建工作线程1。
thread t3(&AA::workfunc, &aa,2); // 创建工作线程2。
thread t4(&AA::workfunc, &aa,3); // 创建工作线程3。
thread t5(&AA::sendfunc, &aa); // 创建发送线程。
logfile.write("已启动全部的线程。\n");
while (true)
{
sleep(30);
// 可以执行一些定时任务。
}
return 0;
}
// 初始化服务端的监听端口。
int initserver(const int port)
{
int sock = socket(AF_INET,SOCK_STREAM,0);
if (sock < 0)
{
logfile.write("socket(%d) failed.\n",port); return -1;
}
int opt = 1; unsigned int len = sizeof(opt);
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,len);
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
if (bind(sock,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0 )
{
logfile.write("bind(%d) failed.\n",port); close(sock); return -1;
}
if (listen(sock,5) != 0 )
{
logfile.write("listen(%d) failed.\n",port); close(sock); return -1;
}
fcntl(sock,F_SETFL,fcntl(sock,F_GETFD,0)|O_NONBLOCK); // 把socket设置为非阻塞。
return sock;
}
void EXIT(int sig)
{
signal(sig,SIG_IGN);
logfile.write("程序退出sig=%d。\n\n",sig);
write(aa.m_recvpipe[1],(char*)"o",1); // 通知接收线程退出。
usleep(500); // 让线程们有足够的时间退出。
exit(0);
}
// 从GET请求中获取参数的值strget-GET请求报文的内容name-参数名value-参数值len-参数值的长度。
bool getvalue(const string &strget,const string &name,string &value)
{
// http://192.168.150.128:8080/api?username=eviwbh&passwd=password
// GET /api?username=eviwbh&passwd=password HTTP/1.1
// Host: 192.168.150.128:8080
// Connection: keep-alive
// Upgrade-Insecure-Requests: 1
// .......
int startp=strget.find(name); // 在请求行中查找参数名的位置。
if (startp==string::npos) return false;
int endp=strget.find("&",startp); // 从参数名的位置开始,查找&符号。
if (endp==string::npos) endp=strget.find(" ",startp); // 如果是最后一个参数,没有找到&符号,那就查找空格。
if (endp==string::npos) return false;
// 从请求行中截取参数的值。
value=strget.substr(startp+(name.length()+1),endp-startp-(name.length()+1));
return true;
}