一点多发FTP客户端设计

项目背景

FileZilla FTP Client这款软件只能手动上传文件到FTP,貌似我还没有找到定时扫描某个本地目录,然后执行定时上传的功能。最近遇到一个问题就是:在服务器上部署到很多个FTP客户端定时程序,每个FTP客户端exe可执行程序功能都是类似的,都是将本地服务器中的某个文件夹下的符合文件规则(如*.json,*.xml)文件通过FTP上传到指定FTP服务器上面的某个目录下。但是开的程序太多了,这样如果需要上传多个比如说雷达文件到多个FTP服务器上时,就会开启多个FTP推送客户端程序,这样服务器上面就产生多个exe可执行程序。
如下图所示:
一点多发FTP客户端设计_第1张图片
这样看起来很不好,而且很难区分对应的是哪个FTP程序,为了解决这个问题,最好将FTP推送客户端做成一点多发的形式。

FTP File Upload Version1.0版

关于只上传一个FTP服务器地址的MFC程序可以参考我之前写的博客:VC++ libcurl FTP上传客户端程序

源代码我已经上传到Github和Gitee上面了,使用的C/C++库有libcurl、boost、pugixml

FTPUpload-Github地址
FTPUpload-Gitee地址
FTPUpload是一款基于MFC的FTP推送客户端程序,使用了libcurl实现FTP推送,使用pugixml实现xml配置文件的读写,还使用了Boost库用于目录规则的转换(涉及到日期的)。程序执行流程图如下图所示:
一点多发FTP客户端设计_第2张图片

程序如下图所示:
一点多发FTP客户端设计_第3张图片

FTP File Upload Version2.0版

在第一个版本的基础上做了另外一个版本,增加了Windows服务程序
一点多发FTP客户端设计_第4张图片

FTP File Upload Version3.0版

功能描述:

  • FTP多数据源多FTP目标源推送客户端的实现(支持一点多发):
  • 1.从数据库中获取任务信息,分别获取需要上传的本地文件源的信息(包括需要扫描的本地目录、目录规则、文件规则等)以及
  • FTP目的地信息(包括FTP的远程URL地址、FTP用户名、密码、远程的目录地址等)
  • 2、遍历任务列表
  • 3、针对每个任务,遍历文件源(可能有多个)
  • 4、对于每个文件源,遍历需要上传的FTP地址(可能有多个)
  • 5、针对每个文件源以及某个FTP目的信息的数据上传通道,创建一个FTP上传线程
  • 6、对于单个的FTP上传线程,执行FTP上传,并将上传成功或失败的写入到数据库中,以便多次重复传输(这里要注意剔除哪些在本地目录已经不存在但是在数据库中还有上传记录的文件上传记录信息)

程序执行流程图

程序流程图如下图所示:
一点多发FTP客户端设计_第5张图片

数据库设计

我使用的数据库是Sqlite3书库,选择它主要是因为它轻便而且无需提前安装。主要涉及到三张表:t_task任务表、t_source数据源表、t_dest即FTP目的地表,后续根据需要最好再加一张记录已经上传的文件列表信息,以防止重复上传相同的文件。这三张表的结构如下图示:

  • 表1 t_task
    一点多发FTP客户端设计_第6张图片

  • 表2 t_source
    一点多发FTP客户端设计_第7张图片

  • 表3 t_dest
    一点多发FTP客户端设计_第8张图片

当然,可以选择Sqlite3数据库作为数据源,也可以使用xml配置文件作为数据源。

为了方便我使用的是Sqlite3数据库,简单方便,使用了CodeProject上面一个关于Windows系统下SQLite的C++封装类CppSQLite - C++ Wrapper for SQLite,基于此类简单封装了一下基于Sqlite3的操作。核心代码如下:

// FtpUploadMulti.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include 
#include "TaskDB.h"
#include "FTPUpload.h"
#include 

using namespace std;

/**
 * 功能描述:
 * FTP多数据源多FTP目标源推送客户端的实现(支持一点多发):
 * 1.从数据库中获取任务信息,分别获取需要上传的本地文件源的信息(包括需要扫描的本地目录、目录规则、文件规则等)以及
 * FTP目的地信息(包括FTP的远程URL地址、FTP用户名、密码、远程的目录地址等)
 * 2、遍历任务列表
 * 3、针对每个任务,遍历文件源(可能有多个)
 * 4、对于每个文件源,遍历需要上传的FTP地址(可能有多个)
 * 5、针对每个文件源以及某个FTP目的信息的数据上传通道,创建一个FTP上传线程
 * 6、对于单个的FTP上传线程,执行FTP上传,并将上传成功或失败的写入到数据库中,以便多次重复传输(这里要注意剔除哪些在本地目录已经不存在但是在数据库中还有上传记录的文件上传记录信息)
 */
// FTP上传线程函数
void ftpUploadRun(FTPInfo item)
{
	FTPUpload ftpUpload;
	// 设置源和FTP目的地信息
	// 设置本地文件目录、扫描的目录规则、文件规则
	ftpUpload.set_local_path(item.localPath.c_str(), item.folderDir.c_str(),
		item.localFile.c_str());
	// 设置FTP上传的地址、用户名和密码
	ftpUpload.set_remote_path(item.remoteUrl.c_str(),
		item.remoteUser.c_str(), item.remotePwd.c_str());
	// 执行FTP上传
	ftpUpload.upload();
}

int main()
{
    // 读取任务列表
   
    // 遍历任务列表,获取文件源和FTP目的地

	// 定时执行FTP上传任务
	// 可以针对每个任务创建一个FTP上传线程,或者为每个通道(上传对象)创建一个FTP上传线程
	signal_log lg;
	TaskDB taskDB(lg);
	TaskItemArray taskList;
	//TaskItem item;
	taskDB.init();
	taskList = taskDB.getTaskList();
	// 遍历任务列表
	std::vector<std::thread> thrVec;

	for (auto taskIter = taskList.begin(); taskIter != taskList.end(); taskIter++)
	{
		// 获取源sid
		int sid = taskIter->sourceId;
		// 获取FTP目的地did
		int did = taskIter->destId;
		// 根据sid获取源列表
		SourceItemArray srcList;
		srcList = taskDB.getSourceListById(sid);
		printf("\n--------------- 源信息 ---------------\n");
		for (auto srcIter = srcList.begin(); srcIter != srcList.end(); srcIter++)
		{
			printf("sid: %d, cid: %d, localPath: %s, folderRule: %s, fileRule: %s\n",
				srcIter->sid, srcIter->cid, srcIter->localPath.c_str(), srcIter->folderRule.c_str(), srcIter->fileRule.c_str());
		}
		// 根据did获取FTP目的地信息列表
		DestItemArray destList;
		destList = taskDB.getDestListById(did);
		printf("\n--------------- FTP目的地信息 ---------------\n");
		for (auto destIter = destList.begin(); destIter != destList.end(); destIter++)
		{
			printf("did: %d, cid: %d, ftpUrl: %s, ftpFolder: %s, enableFolder: %s, ftpUser: %s, ftpPwd: %s\n",
				destIter->did, destIter->cid, destIter->ftpUrl.c_str(), destIter->ftpFolder.c_str(),
				destIter->enableFolder ? "true" : "false", destIter->ftpUser.c_str(), destIter->ftpPwd.c_str());
		}

		// 获取线程总数(即FTP数据上传通道的总数:源的数目 * 目的地的数据)
		int threadNum = srcList.size() * destList.size();
		printf("FTP数据上传通道总数(线程总数目): %d\n", threadNum);

		for (auto srcIter = srcList.begin(); srcIter != srcList.end(); srcIter++)
		{
			SourceItem srcItem = *srcIter;
			for (auto destIter = destList.begin(); destIter != destList.end(); destIter++)
			{
				DestItem destItem = *destIter;

				FTPInfo item;
				item.uploadRate = 5;
				item.localPath = srcItem.localPath;
				item.folderDir = srcItem.folderRule;
				item.localFile = srcItem.fileRule;
				item.remoteUrl = destItem.ftpUrl;
				item.remoteUser = destItem.ftpUser;
				item.remotePwd = destItem.ftpPwd;

				// 创建一个FTP上传线程,并将其加入到线程列表中
				thrVec.push_back(std::thread(ftpUploadRun, item));
			}
		}
	}

	/*while (true)
	{
		msleep(10);
	}*/
	getchar();
	for (auto &thr : thrVec)
	{
		if (thr.joinable())
		{
			thr.join();
		}
	}

	return 0;
}

一点多发FTP客户端设计_第9张图片

你可能感兴趣的:(Visual,C++和MFC,软件架构,编程语言学习)