??ftp 数据采集模块主要是使用封装好的 Cftp类写的客户端程序从服务器将数据采集回来。Cftp类是在 ftplib开源库的基础上做的二次封装。
??使用 ftp 进行开发前,先对ftp 进行简单的了解。
一、ftp 协议简单介绍
??ftp(File Transfer Protocol文件传输协议)是基于TCP/IP 协议的应用层协议,用于文件的传输,包括ftp服务器(或服务端)和ftp客户端。
ftp客户端与服务器创建网络连接,请求登录服务器,登录成功后,就可以进行文件传输,主要包括下载文件和上传文件两种操作。
ftp协议很古老,但是,ftp的应用场景仍非常广泛,这是不争的事实。
在Linux系统中,ftp客户端和ftp服务器是操作系统自带的,但不一定会缺省安装。
1、主动模式和被动模式
??ftp有两种模式,分别是port模式(主动模式)和pasv模式(被动模式)。主动模式是客户端先打开一个数据端口传输数据,服务端主动向这个端口发起连接。被动模式是服务端打开一个数据端口传输数据,客户端向这个端口发起连接。连接成功后开始传输文件。
但是无论是主动模式和被动模式,客户端都是在服务端的21端口发送ftp命令的。
1.1、主动模式
??客户端给服务端的21端口发命令说:我要输传文件,我已经打开了自己的20端口,您向我的20端口发起TCP连接,我们来传输文件。服务端知道后,就会主动向客户端的20端口发起连接,连接成功后开始传输文件。
主动模式发数据时,是服务端向客户端的端口发起连接。
1.2、被动模式
??客户端给服务器端的21端口发命令说:我要传输文件。服务器端知道后打开一个空闲的高端口,然后告诉客户端,我已经打开了某某端口,您向我这个端口发起TCP连接,然后我们用这个端口来传输文件。在被动模式下,不管是ftp命令,还是传输数据,都是由客户端向服务端发起TCP连接。
1.3、从主动模式到被动模式
??在很久以前每台电脑都有一个ip地址,ftp只有主动模式,后来出现了共享上网技术,所以也就有了下面的问题。
??共享上网就是多台电脑共享一个公网ip去使用internet,例如某个局域网出口的公网ip是210.33.25.108,当内网用户(192.168.1.100)访问外网的ftp服务器时,如果采用主动模式,192.168.1.100告诉了ftp服务器我需要某个文件和我打开了20端口之后,由于共享上网的原因,192.168.1.100在出网关的时候ip已经被转换成了210.33.25.108,所以ftp服务器端收到的消息是210.33.25.108需要某个文件并打开了20端口,ftp服务器就会尝试连接210.33.25.108的20端口,这样当然不会成功。(内网用户ip地址就像是一个小村庄,外网ip地址就像是一个市。)
??在主动模式中,ftp的两个端口是相对固定的,如果命令端口是n的话,那数据端口就是n-1,也就是说默认情况下,命令端口是21,数据端口就是20,如果您把ftp服务的端口改成了521,那么数据端口就是520,这样配置防火墙很方便,只需要开通两个端口就可以了。但是,在共享上网的环境中无法使用主动模式。
??在被动模式中,默认情况下命令端口是21,数据端口是随机分配的。但是,被动模式中数据端口的范围可以配置,防火墙也可以配置端口范围。
二、ftp 命令
1、登录服务器
方法一:输入ftp 服务器ip地址 ,回车后根据提示输入用户名和密码,如下图:
方法二:输入ftp ,用open 服务器ip地址 ,连上服务器后再输入用户名和密码,如下图:
方法三:输入ftp -n 服务器ip地址 ,再输入user 用户名 密码登录 ,如下图:
2、切换工作目录
注意,如果目录名中有特殊符号,如空格,可以用双引号把目录名包含起来。
2.1、查看服务器工作目录
pwd
2.2、切换服务器工作目录
cd 目录名
2.3、切换本地工作目录
lcd 目录名
3、查看服务器上的目录和文件
============================
3.1、列出目录或文件名的详细信息
ls 目录或文件名
dir 目录或文件名
ls和dir都可以用于查看目录和文件信息,常用ls,语法和Linux的ls命令相同。
3.2、仅列出目录和文件名
nlist 目录或文件名 [本地文件名]
1)列出/freecplus目录下的匹配*.h的文件名信息。
2)列出/freecplus目录下的匹配*.h的文件名信息,结果输出到本地的/tmp/freecplus.list文件中。
查看/tmp/freecplus.list内容。
4、下载/上传文件
==================
4.1、文件传输入的模式
ftp的传输模式分为二进制和ASCII码两种模式,二进制模式可以传输任何文件,包括压缩包、可执行程序、图片、视频、音频等,而ASCII模式只能传输.txt、.htm等ascii码文件(文本文件)。在实际开发中,不管什么文件,都用二进制方式传输文件。
1)查看当前的传输模式。
type
2)设定传输模式为二进制。
bin
3)设定传输模式为ASCII。
ascii
示例:
4.2、下载文件
1)下载单个文件。
get/recv 服务器文件名 [本地文件名]
使用说明:
a)下载文件用get和recv都可以。
b)文件名不允许用通配符。
c)服务器文件名和本地文件名可以用绝对路径,如果不写路径,表示当前工作目录。
d)如果本地文件名省略不写,表示把服务器文件下载到本地的当前工作目录,文件名与服务器文件名相同。
2)下载多个文件。
mget 服务器文件1 服务器文件2 服务器文件3 …… 服务器文件n
使用说明:
a)待下载的文件名,可以一一列出来(用空格分隔),也可以用通配符。
b)下载的文件,存放在本地当前工作目录中。
c)下载文件时,会一一提示,如果想关闭都显示信息,先输入prompt命令。
prompt
4.3、上传文件
1)上传单个文件。
put/send 本地文件名 [服务器文件名]
a)上传文件用put和send都可以。
b)文件名不允许用通配符。
c)本地文件名和服务器文件名可以用绝对路径,如果不写路径,表示当前工作目录。
d)如果服务器文件名省略不写,表示把本地文件上传到服务器的当前工作目录,文件名与本地文件名相同。
2)上传多个文件。
mput 本地文件1 本地文件2 本地文件3 …… 本地文件n
使用说明:
a)待上传的文件名,可以一一列出来(用空格分隔),也可以用通配符。
b)上传的文件,存放在服务器当前工作目录中。
c)上传文件时,会一一提示,如果想关闭都显示信息,先输入prompt命令。
prompt
5、其它ftp命令
================
1)重命名服务器上的文件
rename 旧文件名 新文件名
2)删除ftp服务器上单个文件
delete 文件名
3)删除多个文件。
mdelete 文件名1 文件名2 文件名3 …… 文件名n
4)在服务器上创建目录。
mkdir pathname
5)删除服务器上的目录。
rmdir pathname
6)切换传输模式。
passive
7)显示帮助信息。
help [命令名]
显示ftp命令的帮助信息,如果不输入命令名,则显示全ftp命令的帮助信息。
8)退出ftp。
bye
三、使用 ftp 协议开发的客户端程序
??ftp客户端程序主要的目的是从服务端下载数据或者上传数据。首先要了解 ftp从服务端获取数据的基本流程。
1.ftp 下载服务器数据的流程
??(1)登录 ftp 服务器,用户名和密码
??(2)选择主动模式或者被动模式,一般是被动模式
??(3)找到要下载数据的路径,也就是要切换服务器的工作目录
??(4)切换了工作目录,如果有多种数据类型,匹配自己要找的数据类型,比如说我要下载 .txt 类型,就匹配 .txt 类型文件
??(5)执行下载命令下载自己想要的数据
??(6)指定存放数据的本地路径
??(7)检查是否下载成功,将已经下载的文件的文件名放到本地指定文件中。
??所以程序的参数应该包含前面的流程需求:
2.实现增量下载服务器数据的功能
??增量采集:采集过的文件不再采集,缓解网络压力。实现这个功能要解决的一个重点是:如何知道哪些文件已经被采集,哪些没有被采集,要将他们区分开来。
??(1)这里采用的办法是每次采集数据之前,都要把指定的服务端目录下的文件名,写到客户端的一个文件中,称为要采集文件。
??(2)当从服务端下载了一个文件到客户端,就将这个文件名写到客户端的一个文件中,称为已下载文件。
??(3)当选择了增量采集模式,就把要采集文件的内容导入程序中的要采集容器中,这个步骤要把文件筛选,就是文件有很多种,只要本次 要下载的文件格式。把已经下载文件内容导入已经下载容器中。
??(4)然后比较这两个容器,如果要采集容器中有的文件(其实是文件名和文件最后修改时间),而已经下载容器中没有,就说明本次任务要下载。就把这个文件放到另一个容器中。
3.保证下载文件的完整性措施
??凡是涉及到文件传输的步骤,都要考虑到文件传输后的完整性。导致文件不完整的两个常见的现象:(1)服务端还没有写完文件,客户端就已经下载了;(2)传输过程中,由于网络异常等原因导致
详细文章:https://blog.csdn.net/qq_43403759/article/details/115421725
3.1 服务端未写完文件就客户端就下载
??这个现象比较少见,但是不排除会出现。解决的办法:
给没写完(正在写)的文件加上没有写完的标识(后缀)。比如说要将一批数据写入aaa.txt,在没有写完之前命名为 aaa.txt.tmp,写完了之后重命名为 aaa.txt。客户端指定了要下载的文件格式,下载文件的时候只下载那些完整的文件(比如说文件后缀名为 .txt),不完整的就不获取(比如说 .tmp 的文件后缀名)。
??上面的步骤就已经封装在函数里面了。
3.2 由于传输过程出错导致不完整
??从服务端下载文件,从客户端上传文件到服务端。这两个操作在封装的Cftp类中,分别是get() 函数和 put() 函数。
??为了保证在传输过程中文件的完整性,在这个两个函数的参数中加了一个参数,get()函数加了核对传输前后时间,put() 函数加了核对传输前后文件大小。
(1)get()
??在文件传输之前,获取文件内容的最后修改时间,传输了之后再获取文件的最后的修改时间。对比这两个时间如果不相同,就说明源文件传输完之后被修改了,与传输的目标文件已经不同了,那么我得到这个被修改过后的文件后要丢弃。 ??为什么不核对大小呢?文件内容改变了大小不一定变,但文件内容的最后修改时间一定变。
(2)put()
??这个不用核对时间,因为客户端传输文件给另一端之前都会保证文件是正确的完整的,不会是中间状态的文件,传输的是准备好的文件。所以核对传输前后的文件大小就可以了。
四、封装的 Cftp 类
class Cftp
{
public:
netbuf *m_ftpconn;
unsigned int m_size;
char m_mtime[21];
bool m_connectfailed;
bool m_loginfailed;
bool m_optionfailed;
Cftp();
~Cftp();
void initdata();
bool login(const char *host,const char *username,const char *password,const int imode=FTPLIB_PASSIVE);
bool logout();
bool mtime(const char *remotefilename);
bool size(const char *remotefilename);
bool chdir(const char *remotedir);
bool mkdir(const char *remotedir);
bool rmdir(const char *remotedir);
bool nlist(const char *remotedir,const char *listfilename);
bool get(const char *remotefilename,const char *localfilename,const bool bCheckMTime=true);
bool put(const char *localfilename,const char *remotefilename,const bool bCheckSize=true);
bool ftpdelete(const char *remotefilename);
bool ftprename(const char *srcremotefilename,const char *dstremotefilename);
bool dir(const char *remotedir,const char *listfilename);
bool site(const char *command);
char *response();
};
五、ftp 下载客户端
头文件的获取方式:在freecplus框架里面https://www.freecplus.net/index.html
#include "_public.h"
#include "_ftp.h"
struct st_arg
{
char host[51];
int mode;
char username[31];
char password[31];
char localpath[301];
char remotepath[301];
char matchname[301];
int ptype;
char remotepathbak[301];
char listfilename[301];
char okfilename[301];
int timetvl;
} starg;
Cftp ftp;
CLogFile logfile;
bool _ftpgetfiles();
vector<struct st_fileinfo> vlistfile,vlistfile1;
vector<struct st_fileinfo> vokfilename,vokfilename1;
bool LoadListFile();
bool LoadOKFileName();
bool CompVector();
bool WriteToOKFileName();
bool AppendToOKFileName(struct st_fileinfo *stfileinfo);
void EXIT(int sig);
void _help(char *argv[]);
bool _xmltoarg(char *strxmlbuffer);
int main(int argc,char *argv[])
{
if (argc!=3) { _help(argv); return -1; }
CloseIOAndSignal();
signal(SIGINT,EXIT); signal(SIGTERM,EXIT);
if (logfile.Open(argv[1],"a+")==false)
{
printf("打开日志文件失败(%s)。\n",argv[1]); return -1;
}
if (_xmltoarg(argv[2])==false) return -1;
while (true)
{
if (ftp.login(starg.host,starg.username,starg.password,starg.mode)==false)
{
logfile.Write("ftp.login(%s,%s,%s) failed.\n",starg.host,starg.username,starg.password); sleep(10); continue;
}
_ftpgetfiles();
ftp.logout();
sleep(starg.timetvl);
}
return 0;
}
void EXIT(int sig)
{
logfile.Write("程序退出,sig=%d\n\n",sig);
exit(0);
}
bool _ftpgetfiles()
{
if (ftp.chdir(starg.remotepath)==false)
{
logfile.Write("ftp.chdir(%s) failed.\n",starg.remotepath); return false;
}
if (ftp.nlist(".",starg.listfilename)==false)
{
logfile.Write("ftp.nlist(%s) failed.\n",starg.remotepath); return false;
}
if (LoadListFile()==false)
{
logfile.Write("LoadListFile() failed.\n"); return false;
}
if (starg.ptype==1)
{
LoadOKFileName();
CompVector();
WriteToOKFileName();
vlistfile.clear(); vlistfile.swap(vlistfile1);
}
for (int ii=0;ii<vlistfile.size();ii++)
{
char strremotefilename[301],strlocalfilename[301];
SNPRINTF(strlocalfilename,300,"%s/%s",starg.localpath,vlistfile[ii].filename);
SNPRINTF(strremotefilename,300,"%s/%s",starg.remotepath,vlistfile[ii].filename);
logfile.Write("get %s ...",strremotefilename);
if (ftp.get(strremotefilename,strlocalfilename,true)==false)
{
logfile.WriteEx("failed.\n"); break;
}
logfile.WriteEx("ok.\n");
if (starg.ptype==2) ftp.ftpdelete(strremotefilename);
if (starg.ptype==3)
{
char strremotefilenamebak[301];
SNPRINTF(strremotefilenamebak,300,"%s/%s",starg.remotepathbak,vlistfile[ii].filename);
ftp.ftprename(strremotefilename,strremotefilenamebak);
}
if (starg.ptype==1) AppendToOKFileName(&vlistfile[ii]);
}
return true;
}
bool LoadListFile()
{
vlistfile.clear();
CFile File;
if (File.Open(starg.listfilename,"r") == false)
{
logfile.Write("File.Open(%s) 失败。\n",starg.listfilename); return false;
}
struct st_fileinfo stfileinfo;
while (true)
{
memset(&stfileinfo,0,sizeof(struct st_fileinfo));
if (File.Fgets(stfileinfo.filename,300,true)==false) break;
if (MatchFileName(stfileinfo.filename,starg.matchname)==false) continue;
if (starg.ptype==1)
{
if (ftp.mtime(stfileinfo.filename)==false)
{
logfile.Write("ftp.mtime(%s) failed.\n",stfileinfo.filename); return false;
}
strcpy(stfileinfo.mtime,ftp.m_mtime);
}
vlistfile.push_back(stfileinfo);
}
return true;
}
bool LoadOKFileName()
{
vokfilename.clear();
CFile File;
if (File.Open(starg.okfilename,"r") == false) return true;
struct st_fileinfo stfileinfo;
char strbuffer[301];
while (true)
{
memset(&stfileinfo,0,sizeof(struct st_fileinfo));
if (File.Fgets(strbuffer,300,true)==false) break;
GetXMLBuffer(strbuffer,"filename",stfileinfo.filename,300);
GetXMLBuffer(strbuffer,"mtime",stfileinfo.mtime,20);
vokfilename.push_back(stfileinfo);
}
return true;
}
bool CompVector()
{
vokfilename1.clear(); vlistfile1.clear();
for (int ii=0;ii<vlistfile.size();ii++)
{
int jj=0;
for (jj=0;jj<vokfilename.size();jj++)
{
if ( (strcmp(vlistfile[ii].filename,vokfilename[jj].filename)==0) &&
(strcmp(vlistfile[ii].mtime,vokfilename[jj].mtime)==0) )
{
vokfilename1.push_back(vlistfile[ii]); break;
}
}
if (jj==vokfilename.size())
{
vlistfile1.push_back(vlistfile[ii]);
}
}
return true;
}
bool WriteToOKFileName()
{
CFile File;
if (File.Open(starg.okfilename,"w",false) == false)
{
logfile.Write("File.Open(%s) failed.\n",starg.okfilename); return false;
}
for (int ii=0;ii<vokfilename1.size();ii++)
{
File.Fprintf("<filename>%s</filename><mtime>%s</mtime>\n",vokfilename1[ii].filename,vokfilename1[ii].mtime);
}
return true;
}
bool AppendToOKFileName(struct st_fileinfo *stfileinfo)
{
CFile File;
if (File.Open(starg.okfilename,"a",false) == false)
{
logfile.Write("File.Open(%s) failed.\n",starg.okfilename); return false;
}
File.Fprintf("<filename>%s</filename><mtime>%s</mtime>\n",stfileinfo->filename,stfileinfo->mtime);
return true;
}
void _help(char *argv[])
{
printf("\n");
printf("Using:/htidc/public/bin/ftpgetfiles logfilename xmlbuffer\n\n");
printf("Sample:/htidc/public/bin/ftpgetfiles /log/shqx/ftpgetfiles_surfdata.log \"<host>172.16.0.15:21</host><port>21</port><mode>1</mode><username>oracle</username><password>te.st1234TES@T</password><localpath>/data/shqx/ftp/surfdata</localpath><remotepath>/data/shqx/sdata/surfdata</remotepath><matchname>SURF_*.TXT,*.DAT</matchname><ptype>1</ptype><remotepathbak></remotepathbak><listfilename>/data/shqx/ftplist/ftpgetfiles_surfdata.list</listfilename><okfilename>/data/shqx/ftplist/ftpgetfiles_surfdata.xml</okfilename><timetvl>30</timetvl>\"\n\n\n");
printf("本程序是数据中心的公共功能模块,用于把远程FTP服务器的文件采集到本地目录。\n");
printf("logfilename是本程序运行的日志文件。\n");
printf("xmlbuffer为文件传输的参数,如下:\n");
printf("<host>118.89.50.198:21</host> 远程服务器的IP和端口。\n");
printf("<mode>1</mode> 传输模式,1-被动模式,2-主动模式,缺省采用被模式。\n");
printf("<username>wucz</username> 远程服务器FTP的用户名。\n");
printf("<password>test1234TEST</password> 远程服务器FTP的密码。\n");
printf("<localpath>/tmp/ftpget</localpath> 本地文件存放的目录。\n");
printf("<remotepath>/tmp/gzrad</remotepath> 远程服务器存放文件的目录。\n");
printf("<matchname>*.GIF</matchname> 待采集文件匹配的文件名,采用大写匹配,"\
"不匹配的文件不会被采集,本字段尽可能设置精确,不允许用*匹配全部的文件。\n");
printf("<ptype>1</ptype> 文件采集成功后,远程服务器文件的处理方式:1-什么也不做;2-删除;3-备份,如果为3,还要指定备份的目录。\n");
printf("<remotepathbak>/tmp/gzradbak</remotepathbak> 文件采集成功后,服务器文件的备份目录,此参数只有当ptype=3时才有效。\n");
printf("<listfilename>/oracle/qxidc/list/ftpgetfiles_surfdata.list</listfilename> 采集前列出服务器文件名的文件。\n");
printf("<okfilename>/oracle/qxidc/list/ftpgetfiles_surfdata.xml</okfilename> 已采集成功文件名清单,此参数只有当ptype=1时有效。\n");
printf("<timetvl>30</timetvl> 采集时间间隔,单位:秒,建议大于10。\n\n");
}
bool _xmltoarg(char *strxmlbuffer)
{
memset(&starg,0,sizeof(struct st_arg));
GetXMLBuffer(strxmlbuffer,"host",starg.host);
if (strlen(starg.host)==0) { logfile.Write("host is null.\n"); return false; }
GetXMLBuffer(strxmlbuffer,"mode",&starg.mode);
if ( (starg.mode!=1) && (starg.mode!=2) ) starg.mode=1;
GetXMLBuffer(strxmlbuffer,"username",starg.username);
if (strlen(starg.username)==0) { logfile.Write("username is null.\n"); return false; }
GetXMLBuffer(strxmlbuffer,"password",starg.password);
if (strlen(starg.password)==0) { logfile.Write("password is null.\n"); return false; }
GetXMLBuffer(strxmlbuffer,"localpath",starg.localpath);
if (strlen(starg.localpath)==0) { logfile.Write("localpath is null.\n"); return false; }
GetXMLBuffer(strxmlbuffer,"remotepath",starg.remotepath);
if (strlen(starg.remotepath)==0) { logfile.Write("remotepath is null.\n"); return false; }
GetXMLBuffer(strxmlbuffer,"matchname",starg.matchname);
if (strlen(starg.matchname)==0) { logfile.Write("matchname is null.\n"); return false; }
GetXMLBuffer(strxmlbuffer,"ptype",&starg.ptype);
if ( (starg.ptype!=1) && (starg.ptype!=2) && (starg.ptype!=3) ){ logfile.Write("ptype is error.\n"); return false; }
GetXMLBuffer(strxmlbuffer,"remotepathbak",starg.remotepathbak);
if ((starg.ptype==3) && (strlen(starg.remotepathbak)==0) ) { logfile.Write("remotepathbak is null.\n"); return false; }
GetXMLBuffer(strxmlbuffer,"listfilename",starg.listfilename);
if (strlen(starg.listfilename)==0) { logfile.Write("listfilename is null.\n"); return false; }
GetXMLBuffer(strxmlbuffer,"okfilename",starg.okfilename);
if ((starg.ptype==1) && (strlen(starg.okfilename)==0)) { logfile.Write("okfilename is null.\n"); return false; }
GetXMLBuffer(strxmlbuffer,"timetvl",&starg.timetvl);
if (starg.timetvl==0) { logfile.Write("timetvl is null.\n"); return false; }
return true;
}
|