目录
c++ ftp上传文件有三种方式,分别有
-
Qt4 QFtp
-
Qt5 QNetworkAccessManager
-
POCO FTPClientSession
一 各种方式比较
其中QFtp在QT5中已经丢弃了,无法使用,且源码编译,需要解决编码问题,异常处理不友好; QNetworkAccessManager对于一些文件操作不能实现,如创建目录等;
POCO库功能齐全,可以进行文件操作、上传方式改变等。
下面列出QNetworkAccessManager及poco两种实现方式
二 QNetworkAccessManager
这里仅用到文件上传的功能,所以代码主要功能包含url设置和文件上传ftpPut;
FtpQNetWork.h
class FtpQNetWork : public FtpBase {
public:
FtpQNetWork();
virtual ~FtpQNetWork();
virtual bool ftpPut(const QString &localPath, const QString &serverPath,
const QJsonObject &obj = QJsonObject());
private:
void setFtpInfos(const QJsonObject &obj, QUrl &url);
private:
QNetworkAccessManager mNetworkMan;
};
FtpQNetWork.cpp
#include "FtpQNetWork.h"
#include <QDebug>
#include <QEventLoop>
#include <QFile>
#include <QNetworkReply>
#include <QUrl>
#include "common/common.h"
FtpQNetWork::FtpQNetWork() {}
FtpQNetWork::~FtpQNetWork() {}
bool FtpQNetWork::ftpPut(const QString &localPath, const QString &serverPath,
const QJsonObject &obj) {
QFile file(localPath);
if (!file.open(QFile::ReadOnly)) {
qDebug() << "file open failed=" << localPath;
return false;
}
QUrl url;
url.setScheme("ftp"); //设置通讯协议
if (obj.isEmpty()) {
setFtpInfos(mJsonFtpCfg, url);
} else {
setFtpInfos(obj, url);
}
url.setPath(serverPath);
qDebug() << "url " << url;
QNetworkReply *reply = mNetworkMan.put(QNetworkRequest(url), file.readAll());
if (reply) {
//通过QEventLoop 阻塞主线程,直到文件上传完成
QEventLoop loop; //将上传文件操作置为同步操作,等待ftp操作完成再执行其他任务
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
if (reply->error() != QNetworkReply::NoError) {
return false;
} else {
int code =
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (code != 200) {
return false;
}
}
reply->deleteLater();
} else {
qDebug() << "ftpPut reply nullptr donothing";
}
return true;
}
//设置用户名密码端口号等信息
void FtpQNetWork::setFtpInfos(const QJsonObject &obj, QUrl &url) {
if (pCom->checkKeyExist(obj, "ip/port/userName/pwd")) {
auto &&ip = obj.value("ip").toString();
auto &&port = obj.value("port").toInt();
auto &&pwd = obj.value("pwd").toString();
auto &&userName = obj.value("userName").toString();
url.setHost(ip);
url.setPort(port);
url.setPassword(pwd);
url.setUserName(userName);
} else {
qDebug() << "not contain ip/port/userName/pwd=" << obj;
}
}
三 POCO实现方式
poco的方式实现需要下载对应的库 编译后使用
3.1 POCO库下载与编译
我是按照这个做的,仅供参考Windows下Poco库的编译和安装_poco 编译-CSDN博客
这个是用系统编译的库,如果使用QT编程,下载源码后尽量在QT中进行编译,以防有些dll不能使用 。我就是系统编译后在项目中使用编译不通过,然后再QT中又编译一遍才可以的。编译完成后,将bin,include,lib文件夹放入项目中,便可以在code中直接引用该库,如下我放的位置:
3.2 本地ftp server下载
http://learning.happymmall.com/ftpserver/FTPServer.rar
下载安装后,便可使用,很简洁
设置好用户密码、共享目录及端口后,便可以使用测试。
注意:即使上面server中设置了共享目录,但在服务器中其实看不到D:\software\ftpTest这一层目录,只能看到最终上传的文件本身,也就是说上传的是服务器根目录,如下图;当然在本地的D:\software\ftpTest是有该层目录的,可以说二者的文件是共通的。
服务器:
本地目录:
3.3 ftp文件上传
FtpPoco.h
namespace Poco {
namespace Net {
class FTPClientSession;
}
} // namespace Poco
class FtpPoco : public FtpBase {
public:
FtpPoco();
virtual ~FtpPoco();
virtual bool login(const QJsonObject ¶);
virtual bool ftpPut(const QString &localPath, const QString &serverPath,
const QJsonObject &obj = QJsonObject());
private:
std::shared_ptr<Poco::Net::FTPClientSession> mClient;
};
#endif // FTPPOCO_H
FtpPoco.cpp
之前的代码对上传结果判断是无效的,因为那只能检测到本地文件是否存在,并不能判断远端ftp服务器中某个文件是否存在,修改后使用try catch的方式进行结果反馈;
#include "FtpPoco.h"
#include <Poco/Net/FTPClientSession.h>
#include <Poco/StreamCopier.h>
#include <QString>
#include <fstream>
#include <iostream>
#include "common/common.h"
FtpPoco::FtpPoco() {
mClient = std::make_shared<Poco::Net::FTPClientSession>();
}
FtpPoco::~FtpPoco() {}
bool FtpPoco::login(const QJsonObject ¶) {
if (mClient) {
if (pCom->checkKeyExist(para, "ip/port/userName/pwd")) {
auto &&port = para.value("port").toInt();
auto &&pwd = para.value("pwd").toString().toStdString();
auto &&ip = para.value("ip").toString().toStdString();
auto &&userName = para.value("userName").toString().toStdString();
//使用try catch的方式辅助判断登录状态
try {
if (!mIsLoginFlag) {
//使用 用户名 密码 端口 ip 建立ftp连接
mClient->open(ip, port, userName, pwd);
}
//登录
mIsLoginFlag = mClient->isLoggedIn();
if (mIsLoginFlag) {
qDebug() << "login success";
} else {
qDebug() << "login failed";
}
} catch (std::exception &e) {
qDebug() << "login exception e=" << e.what();
mIsLoginFlag = false;
}
}
} else {
qDebug() << "mClient nullptr";
}
return mIsLoginFlag;
}
bool FtpPoco::ftpPut(const QString &localPath, const QString &serverPath,
const QJsonObject &obj) {
(void)obj;
login(mJsonFtpCfg);
bool flag = false;
auto &&srcFn = localPath.toStdString();
auto &&destFn = serverPath.toStdString();
//poco中上传文件 没有可以判断结果状态的方法,这里利用try catch来进行判断上传结果
try {
auto &&newDir = destFn.substr(0, destFn.find_last_of("/"));
if (mClient) {
mClient->setWorkingDirectory(newDir);
std::ifstream ifStr(srcFn, std::ios::binary);
std::ostream &ostr = mClient->beginUpload(destFn);
ostr << ifStr.rdbuf();
mClient->endUpload();
flag = true;
ifStr.close();
}
} catch (std::exception &e) {
qDebug() << "has a exception=" << e.what();
}
return flag;
}
四 总结
代码中可以观察到二者共有一个父类FtpBase,将两种方式封装起来,便于后续的使用。
FtpBase.h
#ifndef FTPBASE_H
#define FTPBASE_H
class QString;
#include <QJsonObject>
#include <QObject>
class FtpBase : public QObject {
public:
FtpBase();
virtual ~FtpBase();
virtual void init(const QJsonObject &ftpObj);
virtual bool login(const QJsonObject ¶);
virtual bool ftpPut(const QString &localPath, const QString &serverPath,
const QJsonObject &obj = QJsonObject());
protected:
bool mIsLoginFlag;
QJsonObject mJsonFtpCfg;
};
#endif // FTPBASE_H
FtpBase.cpp
#include "FtpBase.h"
#include <QString>
#include "common/common.h"
FtpBase::FtpBase() : mIsLoginFlag(false) {}
FtpBase::~FtpBase() {}
//调用init方法 用于登录信息的初始化
void FtpBase::init(const QJsonObject &ftpObj) {
mJsonFtpCfg = ftpObj;
qDebug() << "mJsonFtpCfg=" << mJsonFtpCfg;
}
bool FtpBase::login(const QJsonObject ¶) {
(void)para;
return false;
}
bool FtpBase::ftpPut(const QString &localPath, const QString &serverPath,
const QJsonObject &obj) {
(void)localPath;
(void)serverPath;
(void)obj;
return false;
}
注意:若使用CMake编译配置,设置Poco的使用库时要注意当前编译模式Debug or release等,不同编译模式压迫使用对应的库
set(pocoNetPre ${thirdpartyPre}/Poco/${CMAKE_BUILD_TYPE})
if(CMAKE_BUILD_TYPE STREQUAL Debug)
set(pocoNetLib ${pocoNetPre}/lib/PocoNetd.lib)
set(pocoFoundationLib ${pocoNetPre}/lib/PocoFoundationd.lib)
else()
set(pocoNetLib ${pocoNetPre}/lib/PocoNet.lib)
set(pocoFoundationLib ${pocoNetPre}/lib/PocoFoundation.lib)
持续更新。。