文章目录
在前面的步骤中,已经模拟生成了气象站点的气象观测数据,这个就相当于模拟了服务端的数据源(产生气象观测数据)。
接下来要完成的就是,从服务端那里将观测数据采集回来,用的方法就是利用ftp从服务端下载数据。现在就是要设计一个ftp采集模块,从服务端采集数据。
一、ftp 下载服务器数据的流程
在设计 ftp 采集模块之前,先来回顾一下利用ftp从服务端下载数据的大致过程。
(1)登录 ftp 服务器,用户名和密码
(2)选择主动模式或者被动模式,一般是被动模式
(3)找到要下载数据的路径,也就是要切换服务器的工作目录
(4)切换了工作目录,如果有多种数据类型,匹配自己要找的数据类型,比如说我要下载 .txt 类型,就匹配 .txt 类型文件
(5)执行下载命令下载自己想要的数据
(6)指定存放数据的本地目的
(7)检查是否下载成功
二、设计 ftp 采集数据模块
1.设计采集模块的参数
只要是使用ftp协议从服务器获取数据,上面的流程是肯定要执行的,现在就来设计这个模块。
综上,这个模块的参数应该有:
(1)程序运行要记录日志文件
我们可能要利用 ftp 采集模块采集多种数据,现在是采集气象观测数据,所以命名为:
/oracle/qxidc/log/ftpgetfile_surfdata.log
(2)服务器的IP、端口、用户名、密码(要登录ftp服务器)
登录 ftp 服务器要知道 IP地址指定端口,现在把 IP地址和端口(被动模式一般选择21端口)放在一起作为一个参数:
参数名为:host
用户名参数为:username
密码参数为:password
(3)传输模式,pasv和port,可选字段,缺省采用pasv模式。(一般选择被动模式)
传输模式参数名为:mode,传输模式有主被动模式,定义了两个宏,主动模式为 2,被动模式为 1.
(4)本地文件存放的目录。(采集回来的文件放在哪里)
采集回来气象数据放在 /oracle/qxidc/data/surfdata 中,参数名为 :localpath。
(5)远程服务器存放文件的目录。(要采集的文件放在服务器的什么位置)
参数名为:remotepath。数据源是自拟的,之前把模拟生成气象观测数据放在 /oracle/qxidc/data/ftp/surfdata 下,现在去那个目录获取数据即可。
(6)文件采集成功后,远程服务器文件的处理方式:1-什么也不做;2-删除;3-备份,如果为3,还要指定备份的目录。
备份目录是在服务端的,参数名为:remotepathbak,现在设置为:/oracle/qxidc/data/ftp/surdatabak
(7)实现文件的增量采集。已经取过的文件不用再取。缓解网络的压力。
要实现增量采集,要知道哪些文件要采集,哪些文件已经采集回来了。所以在本地还要有这两个文件。
要采集的文件名放在本地的 /oracle/qxidc/list 目录,文件名为:ftpgetfile_surfdata.list 。参数名为:listfilename
已经采集的文件的文件名放在本地的 /oracle/qxidc/list/ftpgetfile_surfdata.xml。参数名为 okfilename
(8)待采集文件匹配的文件名,不匹配的文件不会被采集,本字段尽可能设置精确,不允许用* 匹配全部的文件。(筛选文件)
当服务器中有多种文件时,要能获取指定的文件类型,这个参数名为:matchname
2.处理采集模块的参数
虽然设计了上面的参数,但是会出现一个问题就是参数很多,在调试程序的时候,或者程序中要使用这些参数 argv[i],里面的i的取值就很多,不方便程序的使用。
现在采用将一些参数合并成一个 XML参数(如下图所示),这样总体上减少了模块的参数数量,总体上看只有3个参数。
所以以后遇到参数多的程序,可以采用这种方法。
将一些参数合并了之后,只要利用XML解析函数将这些参数解析出来,就能很方便地使用。
3.设计模块的函数
3.1 模块名称
这个模块是 利用 ftp 从服务器获取文件,所以这个模块就叫做 ftpgetfile
3.2 提示程序
一般程序的提示信息是放在 main 函数中,但是这个模块的提示信息太长了,把它在 main 函数中,会让 main 显得冗长,所以现在把提示信息分离出去。
提示信息一般包括程序所要的参数,如何使用。
函数名为:_help
参数:这个程序是提示使用者如何使用这个的,所以会使用到模块的(上文说的)那些参数。所以 _help 函数的参数为,模块的参数的地址。
返回值:这个函数的作用是用来提示的,可以不用返回值 void
4.设计XML参数解析函数
因为把ftp文件传输的参数合并成一个XML的字符串,要使用里面的参数,需要将参数解析出来
4.1 函数的参数
4.1.1 设计函数
要解析XML字符串参数,要将XML 字符串给解析函数,所以解析函数的参数就是来接收XML字符串。
返回值:bool
函数名:_xmltoarg ,xml解析到参数
bool _xmltoarg(char *strxmlbuffer);
4.1.2 具体实现
(1)因为把ftp文件传输的参数合并成一个XML的字符串,要使用里面的参数,要将参数解析出来。解析出来了放在一个ftp参数结构体中,声明一个结构体,这个结构体的成员变量有可能有多个子程序要使用,所以结构体声明成全局变量
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];
} starg;
(2)函数代码
bool _xmltoarg(char *strxmlbuffer)
{
// (1)初始化存放解析出来的参数结构体
memset(&starg,0,sizeof(struct st_arg));
// (2)将 xml 解析出来,并且存放到starg 结构体
GetXMLBuffer(strxmlbuffer,"host",starg.host);
GetXMLBuffer(strxmlbuffer,"mode",&starg.mode);
if ( ( starg.mode != 1 ) && ( starg.mode != 2 ) ) starg.mode = 1;
GetXMLBuffer(strxmlbuffer,"username",starg.username);
GetXMLBuffer(strxmlbuffer,"password",starg.password);
GetXMLBuffer(strxmlbuffer,"localpath",starg.localpath);
GetXMLBuffer(strxmlbuffer,"remotepath",starg.remotepath);
GetXMLBuffer(strxmlbuffer,"listfilename",starg.listfilename);
GetXMLBuffer(strxmlbuffer,"okfilename",starg.okfilename);
return true;
}
4.2 函数解析效果
用gdb调试,在断点设置在解析函数,将结构体的成员变量显示,解析正确。
5.设计模块的业务流程主程序
上面的步骤把所需的参数都解析出来了,接下就是使用这些参数去完成 ftp 文件传输。ftp 文件传输的大致步骤已经在上面陈述过,会发现有很多步骤的。如果每一个步骤设计一个函数去处理,把他们都放在main函数中,就会显得不简洁。
所以可以把 ftp文件传输所涉及的一些步骤函数,放在一个业务流程主函数里面,由业务流程主函数去调用步骤函数,再由main 函数调用业务流程主函数,这样就会很简洁。
现在就来设计这个业务流程主函数:
(1)函数名:
_ftpgetfile——ftp获取文件
(2)函数的参数:
这个函数就是文件传输的过程,要使用的变量就是 参数结构体里面的参数,由于之前是把参数结构体定义为全局变量,所以目前这个函数不用传参。
(3)函数返回值:
bool,成功或失败
5.1 登录ftp 服务器
//3.1 ftp 文件传输的第一步登录,ftp服务器
// 登录ftp 服务器需要的参数:IP地址和端口号,用户名和密码,指定模式
int mode;
if ( starg.mode == 1 ) mode = FTPLIB_PASSIVE; // 被动模式
if ( starg.mode == 2 ) mode = FTPLIB_PORT; // 主动模式
if ( ftp.login(starg.host,starg.username,starg.password,mode) == false )
{
logfile.Write("ftp.login(%s,%s,%s,%d)failed.\n",starg.host,starg.username,\
starg.password,mode);
return false;
}
logfile.Write("ftp.login 成功。\n");
5.2 切换工作目录
登录了服务器之后,要获取文件,先切换到要获取的文件所在目录
if ( ftp.chdir(starg.remotepath) == false )
{
logfile.Write("ftp.chdir(%s)failed.\n",start.remotepath);
return false;
}
5.3 获取工作目录下的文件文件
(1)切换了工作目录之后,看看工作目录下有什么文件,并将这些文件的文件名导入本地存放服务器某个目录的文件列表的文件。
(2)代码
// 3.3 列出工作目录下的文件名,并将这些文件名导入本地存放服务器某个目录的文件列表的文件
// 本地存放服务器某个目录的文件列表的文件为:starg.listfilename
if ( ftp.nlist(".",starg.listfilename) == false )
{
logfile.Write("ftp.nlist(%s)failed.\n",starg.listfilename);
return false;
}
logfile.Write("ftp.nlist(%s) 成功!\n",starg.listfilename);
(3)测试代码
这是工作目录下的一部分文件。
查看本地 ftpgetfile_surfdata.list 中的内容,成功获取工作目录下的文件名。
5.4 增量采集
增量采集:采集过的文件不再采集,缓解网络压力。如何实现增量采集:
要解决的一个难点就是:如何知道哪些文件已经被采集,哪些没有被采集,要将他们区分开来
首先我们肯定知道,被采集过的文件和没有被采集过的文件应该是放在两个不同的地方(文件或变量)
之前说过:不要直接操作源数据,所以当程序需要到这些数据时,是先导入到程序中的容器中
现在把没有采集的文件名的容器叫做 vnlist
存放已经采集的文件名的容器称为:voklist
下载文件时,就下载 vnlist里面的
第一次下载时,可以直接下载 vnlist 里面的内容
但是到现在还是没有说明如何实现增量采集
也就是,第一次以后的下载要如何处理,才能实现这个需求
第一次下载后,每次执行这个业务流程程序,都会重新获取一次(nlist)工作目录的文件名,然后保存在本地的文件中,也就是 /oracle/qxidc/list/ftpgetfile_surfdata.list
所以我们将 ftpgetfile_surfdata.list 里面的文件名与 vnlist 、voklist 中的文件名比较
如果这个文件名都存在在两个容器中,就说明已经被采集过了,那么要将这个文件放入暂时voklist1中,表示文件已经被采集。更新容器中的内容。其实增量也可以说是更新。
如果这个文件名只是在 vnlist中,说明还没有被采集,需要去采集。同时,把这个文件暂时放入>另一个容器里面 vnlist1 ,然后用 vnlist1的内容覆盖vnlist的内容(相当于更新)。下载的时候直接下载vnlist中的文件名,这里会有一个问题:为什么不直接下载vnlist1中的,还要去覆盖vnlist。因为 vnlist1是一个中间变量,暂时存放的,在后面的程序使用的是 vnlist。
为什么要需要到vnlist1 和 voklist1 呢?因为每次执行业务流程程序可能会有新的文件产生,要用到他们来暂时存放,然后才更新 vnlist 和 voklist 。
(1)声明所需的变量
下载文件,通常还会要知道文件的时间,所以容器中也应该有文件的时间,所以这些容器是有文件名,还有文件时间,要用一个结构体来表示,所以容器中存放的是结构体
实现增量采集要使用到两种类型的容器,容器存放的是文件信息结构体,以先声明容器中的结构体和这两种容器
因为有许多的程序要使用到,定义成全局变量。
// 4.1 声明文件信息结构体:包括文件名和文件时间
struct st_fileInfo
{
char filename[301];
char mtime[21];
};
// 4.2 声明定义两种容器
vector<struct st_fileInfo> vlistfilename,vlistfilename1;
vector<struct st_fileInfo> vokfilename,vokfilename1;
(2)将 ftpgetfile_surfdata.list 导入程序的 vlistfilename
将nlist获取到的文件名—放在本地的 ,这个文件是存放服务器的指定工作目录里面的文件。/oracle/qxidc/list/ftpgetfile_surfdata.list将这个文件里面的内容导入 vlistfilename 中,以便下载使用
bool LoadListFileToVector()
{
//(1)要打开文件,涉及文件操作类
CFile ListFile;
if ( ListFile.Open(starg.listfilename,"r") == false )
{
logfile.Write("ListFile.Open(%s)failed.\n",starg.listfilename);
return false;
}
logfile.Write("ListFile.Open(%s)成功.\n",starg.listfilename);
// (2)用循环每次读取ftpgetfile_surfdata.list的一条记录,并注入容器,先清空容器
vlistfilename.clear();
// (3)将读取出来的记录暂时存放在文件信息结构体中,所以先定义一个文件信息结构体
struct st_fileInfo stfileInfo;
while(true)
{
memset(&stfileInfo,0,sizeof(struct st_fileInfo));//初始化文件信息结构体
if ( ListFile.Fgets(stfileInfo.filename,300,true) == false )
{
logfile.Write("ListFile.Fgets(%s)读取文件完毕。\n",starg.listfilename);
break;
}
logfile.Write("ListFile.Fgets(%s)成功进入文件读取文件\n",starg.listfilename);
//(4)ftpgetfile_surfdata.list 文件中可能有多种文件类型,要匹配我要下载的文件类型
if ( MatchStr(stfileInfo.filename,starg.matchname) == false )
{
logfile.Write("MatchStr(%s)与(%s)不匹配\n",stfileInfo.filename,\
starg.matchname);
continue;
}
//(5)如果ptype=1,也就是采集了文件后,服务器对对应的文件什么也不做
// 但是要使用文件的时间,所以要获取文件时间
if ( starg.ptype == 1 )
{
// (6) 获取服务器对应文件的最后修改时间
if ( ftp.mtime(stfileInfo.filename) == false )
{
logfile.Write("ftp.mtime(%s)failed.\n",stfileInfo.filename);
return false;
}
// (7)成功获取文件时间,赋值给 stfileInfo.mtime
strcpy(stfileInfo.mtime,ftp.m_mtime);
}
// (8)将文件信息结构体注入容器
vlistfilename.push_back(stfileInfo);
logfile.Write("vlistfilename.push_back filename=%s,mtime=%s\n",\
stfileInfo.filename,stfileInfo.mtime);
}
return true;
}
(3)匹配文件类型
在将 qxidc/list/ftpgetfile_surfdata.list 导入程序的时候,应该要匹配我需要下载的文件类型,因为这个文件是将服务器的某个指定目录的文件名都写入。所以要使用 MatchStr 函数进行筛选。
在读取文件中的记录时,要注意一个问题:从文件中读取的每一行末尾都有一个换行符,读取出来了要将换行符(\n)去掉。
去掉换行符的操作已经封装在 Fgets() 里面,要去掉换行符时,要加上true 这个参数。
如果不去掉换行符,在使用MatchStr 函数时,就会出现不匹配的错误(多了一个换行符)。如下图
下图是加上了 true 这个参数,使用gdb 调试时,换行符会被去掉。
下图是没有使用 true 这个参数,使用gdb 调试时,换行符还存在。使用匹配函数时,就会匹配不上。
(4)将ftpgetfile_surfdata.xml 导入程序中的 vokfilename
增量采集:要将还没有被采集与被采集的文件筛选出来,核心点就是是拿 vlistfilename 容器与 vokfilename 容器对比。要将已采集文件的内容导入程序中的 vokfilename 容器中,才能进行比较。
所以现在要先设计一个函数将 /oracle/qxidc/list/ftpgetfile_surfdata.xml 这个文件的内容导入程序中。
// 4.5 将本地的<okfilename>/oracle/qxidc/list/ftpgetfile_surfdata.xml 这个文件的内容导入程序中。
// 也就是导入 vokfilename 中,因为增量采集主要是与 vokfilename 对比,所以对比之前要把文件的内容
// 导入 okfilename 中,以便使用。
bool LoadOkFileToVector()
{
//(1) 先清空容器
vokfilename.clear();
//(2) 从文件导入,涉及文件操作
CFile Okfile;
if ( Okfile.Open(starg.okfilename,"r") == false )
{
logfile.Write("Okfile.Open(%s)failed.\n",starg.okfilename);
return false;
}
// (3) 读取文件,将读取出来的信息,暂时放在一个字符数组里面,
//然后放在文件信息结构体里面,所以先定义字符数组和一个信息结构体
struct st_fileInfo stfileInfo;
char strbuffer[301];
// (4) 用while 读取文件中的信息,放入信息结构体,最后注入容器
while(true)
{
// (5)初始化结构和数组
memset(strbuffer,0,sizeof(strbuffer));
memset(&stfileInfo,0,sizeof(struct st_fileInfo));
//(6)从文件中读取一条记录,并去掉记录末尾的换行符
if ( Okfile.Fgets(strbuffer,300,true) == false )
{
logfile.Write("Okfile.Fgets(%s)完毕。\n",starg.okfilename);
break;
}
// (7) 读取出来的记录是 xml字符串,要将xml字符串拆分
// xml字符串的格式为:
//<filename>SURF_ZH_20210408145801_1803455.txt</filename><mtime>20210408145801</mtime>
GetXMLBuffer(strbuffer,"filename",stfileInfo.filename,300);
GetXMLBuffer(strbuffer,"mtime",stfileInfo.mtime,20);
// (8) 将信息结构体注入容器
vokfilename.push_back(stfileInfo);
logfile.Write("voklistfile filename=%s,mtime=%s,注入成功\n",\
stfileInfo.filename,stfileInfo.mtime);
}
return true;
}
(5)比较容器区分已采集与未采集
增量采集的一个重要的步骤就是容器比较,由上面的步骤可以得到 vlistfilename 和 vokfilename 这两个容器。
注意,如果是第一次采集,那么 okfilename 里面的内容是空的,那么 vokfilename 容器也是空的。
经过容器比较之后,实现增量采集只需下载 vlistfilename 容器里面的文件名,已经采集过的文件名放在 vokfilename 容器中。
// 4.6比较容器
bool CompVector()
{
//(1)要使用到 vlistfilename1 和 vokfilename1 容器,先清空这个两个容器
vlistfilename1.clear();
vokfilename1.clear();
int jj;
//(2)用嵌套的for循环,查看每一个 vlistfilename 中的文件名是否存在于 vokfilename 中
//是否存在,做一个比较就行,因为他们是字符串,就使用字符串比较方法 strcmp 函数
if ( vokfilename.size() == 0 )
{
for ( int ii=0; ii<vlistfilename.size(); ii++ )
{
vlistfilename1.push_back(vlistfilename[ii]);
}
}
else
for ( int ii=0; ii<vlistfilename.size(); ii++ )
{
for ( jj=0; jj<vokfilename.size(); jj++)
{
// (3) 如果文件名和文件时间相等,说明就存在于 vokfilename 中。
if ( strcmp( vlistfilename[ii].filename, vokfilename[ii].filename )==0&&
strcmp( vlistfilename[ii].mtime, vokfilename[ii].mtime ) == 0 )
{
// (4) 将这个文件名注入 vokfilename1 中
vokfilename1.push_back( vlistfilename[ii] );
break;
}
}
// (5) 不在 vokfilename 中,没有被采集,就放入 vlistfilename1 中
if ( jj == vokfilename.size() )
{
vlistfilename1.push_back( vlistfilename[ii] ) ;
}
}
//(6) 查看 vlistfilename1 和 vokfilename1 中的内容
for (int ii=0;ii<vokfilename1.size();ii++)
{
logfile.Write("vokfilename1的文件为: filename=%s,mtime=%s\n",\
vokfilename1[ii].filename,vokfilename1[ii].mtime);
}
for (int ii=0;ii<vlistfilename1.size();ii++)
{
logfile.Write("vlistfilename1的文件为: filename=%s,mtime=%s\n",\
vlistfilename1[ii].filename,vlistfilename1[ii].mtime);
}
return true;
}
(6)将 voklistfilename 容器写入 okfilename 文件
经过容器比较之后,实现增量采集只需下载 vlistfilename 容器里面的文件名,已经采集过的文件名放在 vokfilename 容器中。
得到了已采集的文件名,那么将他们写入 okfilename 文件中。
//4.7 得到已经采集的文件名,将他们写入 okfilename 文件中
bool WriteToOkFileName()
{
if ( vokfilename.size() != 0 ) // 第一次下载文件时,vokfilename.size()=0,没有东西
{
//(1) 写入文件,涉及文件操作
CFile Okfile;
//(2) 打开文件
if ( Okfile.Open(starg.okfilename,"w") == false )
{
logfile.Write("Okfile.Open(%s) failed.\n",starg.okfilename);
return false;
}
logfile.Write("Okfile.Open(%s) 成功.\n",starg.okfilename);
//(3) 写入文件
//(3) 写入文件
for (int ii=0; ii<vokfilename1.size(); ii++ )
{
Okfile.Fprintf("<filename>%s</filename><mtime>%s</mtime>\n",vokfilename1[ii].filename,vokfilename1[ii].mtime);
logfile.Write("Okfile.Fprintf(%s,%s)ok\n",vokfilename1[ii].filename,vokfilename1[ii].mtime);
}
}
return true;
}
(7)下载文件
经过容器比较之后,要下载未采集的文件,只需下载 vlistfilename 中的文件。
bool GetFile()
{
//(1)要求能根据文件名,自动创建目录,所以要使用全路径的文件名,要拼装
//定义字符串来存放全路径文件名
char strremotefilename[301],strlocalfilename[301];
for (int ii=0; ii<vlistfilename.size(); ii++)
{
// (2) 拼装
SPRINTF(strlocalfilename,300,"%s/%s",starg.localpath,vlistfilename[ii].filename);
SPRINTF(strremotefilename,300,"%s/%s",starg.remotepath,vlistfilename[ii].filename);
logfile.Write("SPRINTF: %s ...\n",strremotefilename);
// (3) 下载
if ( ftp.get(strremotefilename,strlocalfilename,true) == false )
{
logfile.WriteEx("ftp.get() failed.\n"); break;
}
logfile.WriteEx("ftp.get(%s),ok.\n",strremotefilename);
// 1) 下载了文件后,要按照需求去处理服务器对应的文件
// 如果 ptype==2, 要删除文件
if ( starg.ptype == 2 )
{
if ( ftp.ftpdelete(strremotefilename) == false )
{
logfile.Write("ftp.ftpdelete(%s) failed.\n",strremotefilename);
return false;
}
logfile.Write("ftp.ftpdelete(%s)成功.\n",strremotefilename);
}
// 2) ptype == 3,转存到备份目录
if ( starg.ptype == 3 )
{
char strremotefilenamebak[301]; // 备份目录的名称
SPRINTF(strremotefilenamebak,300,"%s/%s",starg.remotepathbak,vlistfilename[ii].filename);
//拼装全路径文件名,自动建目录
if( ftp.ftprename(vlistfilename[ii].filename,strremotefilenamebak) == false )
{
logfile.Write("ftp.ftprename(%s)to(%s)failed.\n",\
strremotefilename,strremotefilenamebak);
return false;
}
logfile.Write("ftp.ftprename(%s) to (%s)成功.\n",\
strremotefilename,strremotefilenamebak);
}
// (4)下载了之后追加到 okfilename 文件中
CFile File;
if ( File.Open(starg.okfilename,"a") == false )
{
logfile.Write("File.Open(%s) failed.\n",starg.okfilename); return false;
}
File.Fprintf("<filename>%s</filename><mtime>%s</mtime>\n",vlistfilename[ii].filename,vlistfilename[ii].mtime);
logfile.Write("File.Fprintf:(%s,%s)Writeto(%s) ok\n",vlistfilename[ii].filename,vlistfilename[ii].mtime,starg.okfilename);
}
return true;
}
(8)将已下载文件名写入 ftpgetfile_surfdata.xml
将已经下载的文件的文件名写入本地的 ftpgetfile_surfdata.xml 文件,记录已经下载到本地的文件。
bool WriteToOkFileName()
{
if ( vokfilename.size() != 0 ) // 第一次下载文件时,vokfilename.size()=0,没有东西
{
//(1) 写入文件,涉及文件操作
CFile Okfile;
//(2) 打开文件
if ( Okfile.Open(starg.okfilename,"w") == false )
{
logfile.Write("Okfile.Open(%s) failed.\n",starg.okfilename);
return false;
}
logfile.Write("Okfile.Open(%s) 成功.\n",starg.okfilename);
//(3) 写入文件
for (int ii=0; ii<vokfilename1.size(); ii++ )
{
Okfile.Fprintf("<filename>%s</filename><mtime>%s</mtime>\n",vokfilename1[ii].filename,vokfilename1[ii].mtime);
logfile.Write("Okfile.Fprintf(%s,%s)ok\n",vokfilename1[ii].filename,vokfilename1[ii].mtime);
}
}
return true;
}
6.测试采集模块功能
6.1 检测是否能匹配文件类型
(1)现在要下载的文件类型是 SURF_*.TXT,为了检测是否能匹配文件类型,我在服务器的存放观测数据的文件创建了一个 SURF_ZH_20210427131601.dat,如下图:
(2)启动采集模块程序后,查看 ftpgetfile_surfdata.list (使用 ftplist()时,将服务器指定目录下的文件名,写入这个文件)的内容,已经获取了服务器目录下的文件如下图,
(3)查看已经下载到本地的文件,没有SURF_ZH_20210427131601.dat 文件,说明这个功能完成。如下图
6.2 检测下载文件后,服务器备份对应的文件
(1)先来测试备份的功能,令 ptype=3,启动程序
(2)要下载服务端下图的 SURF_*.TXT 文件,如下图
(3)执行完毕,已经将上述文件下载到本地指定位置
(4)查看服务器的备份目录下的文件,如下图
下载文件在服务端备份成功,该功能完成
6.2 检测下载文件后,服务器删除对应的文件
(1)执行程序前,查看服务端的文件为:
(2)令 ptype =2,本地下载文件后,服务端删除对应文件
(3)执行程序后,查看本地是否下载成功,本地已下载成功,如下图
(4)查看服务端是否删除文件,
已将文件删除,功能完成。
6.4检测增量采集
(1)下载了之后不再下载这个文件,为了检测这个功能,下载成功之后就将这条信息写在程序运行日志里面,如下图:
(2)将 ptype 改为1,下载文件后,对应的文件服务器什么也不做
(3)第一次下载,查看服务器有什么文件
第一次将下载这两个文件
(4)运行程序,查看程序运行日志文件,下载成功 66147 和 66154 文件成功
运行程序的时间为:
(5)第二次运行前,服务器中新增下图两个文件
(6)运行程序,查程序运行日志文件
这次运行的时间为:
下载文件时,从66177 开始下载,并没有下载之前已经下载过的文件,功能完成。
三、总结
1.遇到参数过多的情况,可以采用将参数合并的方法,然后将参数解析出来。
2.参数过多时,防止参数写错,要仔细观察参数是否写对,如果出错在解析参数时要写提示。
3.程序的简洁性,将功能分出去,令主程序简洁
4.善于利用程序运行日志文件,尽量写得详细一些,方便调试程序。
5.要提高程序的稳健性,考虑到用户可能出错。比如填写参数,可能填少。
6.增量采集的重点在域两个容器的比较,再次简单回顾一下这个流程:
1)、用ftp.list()获取,服务器的指定目录下的文件名,并将这些文件名写入客户端指定的文件(listfilename)
2)、将 listfilename 的内容导入程序中的 vlistfilename 容器,我们有可能要下载 listfilename 文件里面的文件名对应的服务端文件。
3)、将 okfilename(已经下载到本地的文件名)文件里面的内容导入程序中的 vokfilename 容器:
vlistfilename.clear(); vlistfilename.swap(vlistfilename1);
4)、比较两个容器:vlistfilename 和 vokfilename。正式得到要下载的文件名,覆盖原来的vlistfilename 容器
5)、下载文件
6)、将下载好的文件的文件名写入 okfilename 文件中