FTP_transfer/ftpgetfiles.cpp
2024-09-12 18:17:01 +08:00

416 lines
17 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "_el.h"
#include "_ftp.h"
using namespace eviwbh;
// 程序退出和信号2、15的处理函数。
void EXIT(int sig);
// 程序运行参数的结构体。
struct st_arg
{
char host[31]; // 远程服务端的IP和端口。
int mode; // 传输模式1-被动模式2-主动模式,缺省采用被动模式。
char username[31]; // 远程服务端ftp的用户名。
char password[31]; // 远程服务端ftp的密码。
char remotepath[256]; // 远程服务端存放文件的目录。
char localpath[256]; // 本地文件存放的目录。
char matchname[256]; // 待下载文件匹配的规则。
int ptype; // 下载后服务端文件的处理方式1-什么也不做2-删除3-备份。
char remotepathbak[256]; // 下载后服务端文件的备份目录。
char okfilename[256]; // 已下载成功文件信息存放的文件。
bool checkmtime; // 是否需要检查服务端文件的时间true-需要false-不需要缺省为false。
int timeout; // 进程心跳超时的时间。
char pname[51]; // 进程名,建议用"ftpgetfiles_后缀"的方式。
} starg;
bool _xmltoarg(const char *strxmlbuffer); // 把xml解析到参数starg结构中。
clogfile logfile; // 日志文件对象。
cftpclient ftp; // 创建ftp客户端对象。
cpactive pactive; // 进程心跳的对象。
void _help(); // 显示帮助文档。
struct st_fileinfo // 文件信息的结构体。
{
string filename; // 文件名。
string mtime; // 文件时间。
st_fileinfo()=default;
st_fileinfo(const string &in_filename,const string &in_mtime):filename(in_filename),mtime(in_mtime) {}
void clear() { filename.clear(); mtime.clear(); }
};
map<string,string> mfromok; // 容器一存放已下载成功文件从starg.okfilename参数指定的文件中加载。
list<struct st_fileinfo> vfromnlist; // 容器二下载前列出服务端文件名的容器从nlist文件中加载。
list<struct st_fileinfo> vtook; // 容器三:本次不需要下载的文件的容器。
list<struct st_fileinfo> vdownload; // 容器四:本次需要下载的文件的容器。
bool loadokfile(); // 加载starg.okfilename文件中的内容到容器vfromok中。
bool loadlistfile(); // 把ftpclient.nlist()方法获取到的list文件加载到容器vfromnlist中。
bool compmap(); // 比较vfromnlist和vfromok得到vtook和vdownload。
bool writetookfile(); // 把容器vtook中的数据写入starg.okfilename文件覆盖之前的旧starg.okfilename文件。
bool appendtookfile(struct st_fileinfo &stfileinfo); // 把下载成功的文件记录追加到starg.okfilename文件中。
int main(int argc,char *argv[])
{
// 第一步计划:从服务器某个目录中下载文件,可以指定文件名匹配的规则。
if (argc!=3) { _help(); return -1; }
// 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程。
// 但请不要用 "kill -9 +进程号" 强行终止。
// closeioandsignal(true); // 关闭0、1、2和忽略全部的信号在调试阶段这行代码可以不启用。
signal(SIGINT,EXIT); signal(SIGTERM,EXIT);
// 打开日志文件。
if (logfile.open(argv[1])==false)
{
printf("打开日志文件失败(%s\n",argv[1]); return -1;
}
// 解析xml得到程序运行的参数。
if (_xmltoarg(argv[2])==false) return -1;
pactive.addpinfo(starg.timeout,starg.pname); // 把进程的心跳信息写入共享内存。
// 登录ftp服务器。
if (ftp.login(starg.host,starg.username,starg.password,starg.mode)==false)
{
logfile.write("ftp.login(%s,%s,%s) failed.\n%s\n",starg.host,starg.username,starg.password,ftp.response()); return -1;
}
// logfile.write("ftp.login ok.\n");
// 进入ftp服务器存放文件的目录。
if (ftp.chdir(starg.remotepath)==false)
{
logfile.write("ftp.chdir(%s) failed.\n%s\n",starg.remotepath,ftp.response()); return -1;
}
// 调用ftpclient.nlist()方法列出服务器目录中的文件名,保存在本地文件中。
if (ftp.nlist(".",sformat("/tmp/nlist/ftpgetfiles_%d.nlist",getpid())) == false)
{
logfile.write("ftp.nlist(%s) failed.\n%s\n",starg.remotepath,ftp.response()); return -1;
}
// logfile.write("nlist(%s) ok.\n",sformat("/tmp/nlist/ftpgetfiles_%d.nlist",getpid()).c_str());
pactive.uptatime(); // 更新进程的心跳。
// 把ftpclient.nlist()方法获取到的list文件加载到容器vfromnlist中。
if (loadlistfile()==false)
{
logfile.write("loadlistfile() failed.\n"); return -1;
}
if (starg.ptype==1)
{
// 加载starg.okfilename文件中的数据到容器vfromok中。
loadokfile();
// 比较vfromnlist和vfromok得到vtook和vdownload。
compmap();
// 把容器vtook中的数据写入starg.okfilename文件覆盖之前的旧starg.okfilename文件。
writetookfile();
}
else
vfromnlist.swap(vdownload); // 为了统一文件下载的代码,把容器二和容器四交换。
pactive.uptatime(); // 更新进程的心跳。
string strremotefilename,strlocalfilename;
// 遍历vdownload容器。
for (auto & aa : vdownload)
{
sformat(strremotefilename,"%s/%s",starg.remotepath,aa.filename.c_str()); // 拼接服务端全路径的文件名。
sformat(strlocalfilename,"%s/%s",starg.localpath,aa.filename.c_str()); // 拼接本地全路径的文件名。
logfile.write("get %s ...",strremotefilename.c_str());
// 调用ftpclient.get()方法下载文件。
if (ftp.get(strremotefilename,strlocalfilename,starg.checkmtime)==false)
{
logfile << "failed.\n" << ftp.response() << "\n"; return -1;
}
logfile << "ok.\n";
pactive.uptatime(); // 更新进程的心跳。
// 如果ptype==1把下载成功的文件记录追加到starg.okfilename文件中。
if (starg.ptype==1) appendtookfile(aa);
// ptype==2删除服务端的文件。
if (starg.ptype==2)
{
if (ftp.ftpdelete(strremotefilename)==false)
{
logfile.write("ftp.ftpdelete(%s) failed.\n%s\n",strremotefilename.c_str(),ftp.response()); return -1;
}
}
// ptype==3把服务端的文件移动到备份目录。
if (starg.ptype==3)
{
string strremotefilenamebak=sformat("%s/%s",starg.remotepathbak,aa.filename.c_str()); // 生成全路径的备份文件名。
if (ftp.ftprename(strremotefilename,strremotefilenamebak)==false)
{
logfile.write("ftp.ftprename(%s,%s) failed.\n%s\n",strremotefilename.c_str(),strremotefilenamebak.c_str(),ftp.response()); return -1;
}
}
}
return 0;
}
void _help() // 显示帮助文档。
{
printf("\n");
printf("Using:/project/tools/bin/ftpgetfiles logfilename xmlbuffer\n\n");
//printf("Sample:/project/tools/bin/procctl 30 /project/tools/bin/ftpgetfiles /log/idc/ftpgetfiles_surfdata.log " \
// "\"<host>192.168.150.128:21</host><mode>1</mode>"\
// "<username>wucz</username><password>oracle</password>"\
// "<remotepath>/tmp/idc/surfdata</remotepath><localpath>/idcdata/surfdata</localpath>"\
// "<matchname>SURF_ZH*.XML,SURF_ZH*.CSV</matchname>"\
// "<ptype>3</ptype><remotepathbak>/tmp/idc/surfdatabak</remotepathbak>\"\n\n");
printf("Sample:/project/tools/bin/procctl 30 /project/tools/bin/ftpgetfiles /log/idc/ftpgetfiles_test.log " \
"\"<host>192.168.150.128:21</host><mode>1</mode>"\
"<username>wucz</username><password>oracle</password>"\
"<remotepath>/tmp/ftp/server</remotepath><localpath>/tmp/ftp/client</localpath>"\
"<matchname>*.TXT</matchname>"\
"<ptype>1</ptype><okfilename>/idcdata/ftplist/ftpgetfiles_test.xml</okfilename>"\
"<checkmtime>true</checkmtime>"\
"<timeout>30</timeout><pname>ftpgetfiles_test</pname>\"\n\n\n");
printf("本程序是通用的功能模块用于把远程ftp服务端的文件下载到本地目录。\n");
printf("logfilename是本程序运行的日志文件。\n");
printf("xmlbuffer为文件下载的参数如下\n");
printf("<host>192.168.150.128:21</host> 远程服务端的IP和端口。\n");
printf("<mode>1</mode> 传输模式1-被动模式2-主动模式,缺省采用被动模式。\n");
printf("<username>wucz</username> 远程服务端ftp的用户名。\n");
printf("<password>oraccle</password> 远程服务端ftp的密码。\n");
printf("<remotepath>/tmp/idc/surfdata</remotepath> 远程服务端存放文件的目录。\n");
printf("<localpath>/idcdata/surfdata</localpath> 本地文件存放的目录。\n");
printf("<matchname>SURF_ZH*.XML,SURF_ZH*.CSV</matchname> 待下载文件匹配的规则。"\
"不匹配的文件不会被下载,本字段尽可能设置精确,不建议用*匹配全部的文件。\n");
printf("<ptype>1</ptype> 文件下载成功后,远程服务端文件的处理方式:"\
"1-什么也不做2-删除3-备份如果为3还要指定备份的目录。\n");
printf("<remotepathbak>/tmp/idc/surfdatabak</remotepathbak> 文件下载成功后,服务端文件的备份目录,"\
"此参数只有当ptype=3时才有效。\n");
printf("<okfilename>/idcdata/ftplist/ftpgetfiles_test.xml</okfilename> 已下载成功文件名清单,"\
"此参数只有当ptype=1时才有效。\n");
printf("<checkmtime>true</checkmtime> 是否需要检查服务端文件的时间true-需要false-不需要,"\
"此参数只有当ptype=1时才有效缺省为false。\n");
printf("<timeout>30</timeout> 下载文件超时时间,单位:秒,视文件大小和网络带宽而定。\n");
printf("<pname>ftpgetfiles_test</pname> 进程名,尽可能采用易懂的、与其它进程不同的名称,方便故障排查。\n\n\n");
}
// 把xml解析到参数starg结构中。
bool _xmltoarg(const char *strxmlbuffer)
{
memset(&starg,0,sizeof(struct st_arg));
getxmlbuffer(strxmlbuffer,"host",starg.host,30); // 远程服务端的IP和端口。
if (strlen(starg.host)==0)
{ logfile.write("host is null.\n"); return false; }
getxmlbuffer(strxmlbuffer,"mode",starg.mode); // 传输模式1-被动模式2-主动模式,缺省采用被动模式。
if (starg.mode!=2) starg.mode=1;
getxmlbuffer(strxmlbuffer,"username",starg.username,30); // 远程服务端ftp的用户名。
if (strlen(starg.username)==0)
{ logfile.write("username is null.\n"); return false; }
getxmlbuffer(strxmlbuffer,"password",starg.password,30); // 远程服务端ftp的密码。
if (strlen(starg.password)==0)
{ logfile.write("password is null.\n"); return false; }
getxmlbuffer(strxmlbuffer,"remotepath",starg.remotepath,255); // 远程服务端存放文件的目录。
if (strlen(starg.remotepath)==0)
{ logfile.write("remotepath is null.\n"); return false; }
getxmlbuffer(strxmlbuffer,"localpath",starg.localpath,255); // 本地文件存放的目录。
if (strlen(starg.localpath)==0)
{ logfile.write("localpath is null.\n"); return false; }
getxmlbuffer(strxmlbuffer,"matchname",starg.matchname,100); // 待下载文件匹配的规则。
if (strlen(starg.matchname)==0)
{ logfile.write("matchname is null.\n"); return false; }
// 下载后服务端文件的处理方式1-什么也不做2-删除3-备份。
getxmlbuffer(strxmlbuffer,"ptype",starg.ptype);
if ( (starg.ptype!=1) && (starg.ptype!=2) && (starg.ptype!=3) )
{ logfile.write("ptype is error.\n"); return false; }
// 下载后服务端文件的备份目录。
if (starg.ptype==3)
{
getxmlbuffer(strxmlbuffer,"remotepathbak",starg.remotepathbak,255);
if (strlen(starg.remotepathbak)==0) { logfile.write("remotepathbak is null.\n"); return false; }
}
// 增量下载文件。
if (starg.ptype==1)
{
getxmlbuffer(strxmlbuffer,"okfilename",starg.okfilename,255); // 已下载成功文件名清单。
if ( strlen(starg.okfilename)==0 ) { logfile.write("okfilename is null.\n"); return false; }
// 是否需要检查服务端文件的时间true-需要false-不需要此参数只有当ptype=1时才有效缺省为false。
getxmlbuffer(strxmlbuffer,"checkmtime",starg.checkmtime);
}
getxmlbuffer(strxmlbuffer,"timeout",starg.timeout); // 进程心跳的超时时间。
if (starg.timeout==0) { logfile.write("timeout is null.\n"); return false; }
getxmlbuffer(strxmlbuffer,"pname",starg.pname,50); // 进程名。
// if (strlen(starg.pname)==0) { logfile.write("pname is null.\n"); return false; }
return true;
}
void EXIT(int sig)
{
printf("程序退出sig=%d\n\n",sig);
exit(0);
}
// 把ftp.nlist()方法获取到的list文件加载到容器vfromnlist中。
bool loadlistfile()
{
vfromnlist.clear();
cifile ifile;
if (ifile.open(sformat("/tmp/nlist/ftpgetfiles_%d.nlist",getpid()))==false)
{
logfile.write("ifile.open(%s) 失败。\n",sformat("/tmp/nlist/ftpgetfiles_%d.nlist",getpid())); return false;
}
string strfilename;
while (true)
{
if (ifile.readline(strfilename)==false) break;
if (matchstr(strfilename,starg.matchname)==false) continue;
if ( (starg.ptype==1) && (starg.checkmtime==true) )
{
// 获取ftp服务端文件时间。
if (ftp.mtime(strfilename)==false)
{
logfile.write("ftp.mtime(%s) failed.\n",strfilename.c_str()); return false;
}
}
vfromnlist.emplace_back(strfilename,ftp.m_mtime);
}
ifile.closeandremove();
//for (auto &aa:vfromnlist)
// logfile.write("filename=%s,mtime=%s\n",aa.filename.c_str(),aa.mtime.c_str());
return true;
}
// 加载starg.okfilename文件中的内容到容器vfromok中。
bool loadokfile()
{
if (starg.ptype!=1) return true;
mfromok.clear();
cifile ifile;
// 注意如果程序是第一次运行starg.okfilename是不存在的并不是错误所以也返回true。
if ( (ifile.open(starg.okfilename))==false ) return true;
string strbuffer;
struct st_fileinfo stfileinfo;
while (true)
{
stfileinfo.clear();
if (ifile.readline(strbuffer)==false) break;
getxmlbuffer(strbuffer,"filename",stfileinfo.filename);
getxmlbuffer(strbuffer,"mtime",stfileinfo.mtime);
mfromok[stfileinfo.filename]=stfileinfo.mtime;
}
//for (auto &aa:mfromok)
// logfile.write("filename=%s,mtime=%s\n",aa.first.c_str(),aa.second.c_str());
return true;
}
// 比较vfromnlist和vfromok得到vtook和vdownload。
bool compmap()
{
vtook.clear();
vdownload.clear();
// 遍历vfromnlist。
for (auto &aa:vfromnlist)
{
auto it=mfromok.find(aa.filename); // 在容器一中用文件名查找。
if (it !=mfromok.end())
{ // 如果找到了,再判断文件时间。
if (starg.checkmtime==true)
{
// 如果时间也相同,不需要下载,否则需要重新下载。
if (it->second==aa.mtime) vtook.push_back(aa); // 文件时间没有变化,不需要下载。
else vdownload.push_back(aa); // 需要重新下载。
}
else
{
vtook.push_back(aa); // 不需要重新下载。
}
}
else
{ // 如果没有找到把记录放入vdownload容器。
vdownload.push_back(aa);
}
}
return true;
}
// 把容器vtook中的内容写入starg.okfilename文件覆盖之前的旧starg.okfilename文件。
bool writetookfile()
{
cofile ofile;
if (ofile.open(starg.okfilename)==false)
{
logfile.write("file.open(%s) failed.\n",starg.okfilename); return false;
}
for (auto &aa:vtook)
ofile.writeline("<filename>%s</filename><mtime>%s</mtime>\n",aa.filename.c_str(),aa.mtime.c_str());
ofile.closeandrename();
return true;
}
// 把下载成功的文件记录追加到starg.okfilename文件中。
bool appendtookfile(struct st_fileinfo &stfileinfo)
{
cofile ofile;
// 以追加的方式打开文件注意第二个参数一定要填false。
if (ofile.open(starg.okfilename,false,ios::app)==false)
{
logfile.write("file.open(%s) failed.\n",starg.okfilename); return false;
}
ofile.writeline("<filename>%s</filename><mtime>%s</mtime>\n",stfileinfo.filename.c_str(),stfileinfo.mtime.c_str());
return true;
}