要在Qt中使用C++实现FTP上传文件功能,首先需要考虑几个关键步骤:
- 遍历指定文件夹下的所有文件。
- 使用Qt的网络模块与FTP服务器建立连接。
- 根据需求上传文件,包括创建不存在的目录、处理同名文件的覆盖或重命名等。
我使用的是`QNetworkAccessManager` 实现文件上传,但`QNetworkAccessManager` 不支持创建目录等其他操作。
因此,创建 FTP 服务器上的目录将需要采取不同的方法。一种可能的解决方案是使用第三方库,或者直接使用 `QTcpSocket` 来发送 FTP 命令。这里我使用的就是 `QTcpSocket` 来发送 FTP 命令
然后,实现FTP上传功能的大致代码如下:
FtpUploader.h:
#ifndef FTPUPLOADER_H
#define FTPUPLOADER_H
#include <QObject>
#include <QNetworkAccessManager>
/**
* @class FtpUploader
* @brief 负责通过FTP协议上传文件
*/
class FtpUploader : public QObject {
Q_OBJECT
public:
/**
* @brief 构造函数,接受FTP服务器的URL、用户名和密码
* @param[in] server FTP服务器的URL
* @param[in] username 用户名
* @param[in] password 用户密码
*/
FtpUploader(const QString &server, const QString &username, const QString &password, QObject *parent = nullptr);
/**
* @brief 上传指定目录下的所有文件
* @param[in] dirPath 文件夹地址
* @return 是否成功
*/
bool uploadDirectory(const QString &dirPath);
private:
/**
* @brief 上传单个文件到FTP服务器。
* @param[in] filePath 本地文件的完整路径
* @param[in] remoteName 文件在服务器上的存储位置和名称
* @return 是否成功
*/
bool uploadFile(const QString &filePath, const QString &remoteName);
/**
* @brief 创建QTcpSocket连接
* @param[in] remoteName 相对目录
*/
int setftpSocket(const QString& remoteName);
/**
* @brief 在FTP服务器创建目录
* @param[in] socket tcpsocket对象
* @param[in] fullPath 目录
*/
void createDirectory(QTcpSocket& socket, const QString& fullPath);
QNetworkAccessManager *manager; // 用于网络通信的管理器。
QString serverUrl; // FTP服务器的URL。
QString username; // FTP用户名。
QString password; // FTP密码。
};
#endif // FTPUPLOADER_H
FtpUploader.cpp:
#include "FtpUploader.h"
#include <QDir>
#include <QFile>
#include <QFileInfoList>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QUrl>
#include <QDirIterator>
#include <QTcpSocket>
#include <QTextStream>
#include <QEventLoop>
FtpUploader::FtpUploader(const QString &server, const QString &username, const QString &password, QObject *parent)
: QObject(parent), serverUrl(server), username(username), password(password)
{
manager = new QNetworkAccessManager(this); // 初始化网络访问管理器。
}
// 遍历目录并上传每个文件。
bool FtpUploader::uploadDirectory(const QString &dirPath)
{
QDir dir(dirPath);
if (!dir.exists())
return;
QDir baseDir = dir;
// 如果dirPath指向一个目录,向上移动一级来获取它的父目录作为基础路径
if (dir.cdUp())
{
baseDir = dir.absolutePath();
}
bool bRet = false;
//遍历目录及其所有子目录下的文件列表
QDirIterator it(dirPath, QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
while (it.hasNext())
{
QString filePath = it.next();
QFileInfo fileInfo(filePath);
QString relativePath = baseDir.relativeFilePath(fileInfo.absoluteFilePath());
bRet = uploadFile(fileInfo.absoluteFilePath(), relativePath);
if (!bRet)
{
return bRet;
}
}
return bRet;
}
// 处理文件上传。
bool FtpUploader::uploadFile(const QString &filePath, const QString &remoteName)
{
QFile *file = new QFile(filePath);
if (!file->open(QIODevice::ReadOnly))
{
delete file;
return false;
}
//创建目录
setftpSocket(remoteName);
QUrl url(serverUrl);
url.setUserName(username);
url.setPassword(password);
url.setPath(remoteName);
QNetworkRequest request(url);
//put方法本质上是向服务器发送一个写入请求,如果同名文件存在,服务器通常会处理这个写入请求并覆盖文件。
QNetworkReply *reply = manager->put(request, file);
// 等待上传完成
QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
// 检查上传结果
if (reply->error() == QNetworkReply::NoError)
{
qDebug() << "Upload successful for file:" << file->fileName();
bRet = true;
}
else
{
qDebug() << "Upload failed for file:" << file->fileName() << ", Error:" << reply->errorString();
bRet = false;
}
file->close();
file->deleteLater();
reply->deleteLater();
return bRet;
}
int FtpUploader::setftpSocket(const QString& remoteName)
{
// 分割路径,得到各级目录和文件名
QStringList pathParts = remoteName.split("/");
// 去除最后一个元素(文件名),只保留目录结构
pathParts.removeLast();
// 重建目录路径
QString directoryPath = pathParts.join("/");
QTcpSocket ftpSocket;
// 连接到 FTP 服务器
ftpSocket.connectToHost("yourIP", 21);
if (!ftpSocket.waitForConnected(30000)) {
qDebug() << "Error connecting to server:" << ftpSocket.errorString();
return -1;
}
// 等待欢迎消息
if (!ftpSocket.waitForReadyRead(30000)) {
qDebug() << "Error reading welcome message:" << ftpSocket.errorString();
return -1;
}
qDebug() << "Server:" << QTextStream(&ftpSocket).readLine();
// 登录到 FTP 服务器
QTextStream(&ftpSocket) << "USER yourUsername\r\n";
ftpSocket.waitForReadyRead(30000);
QTextStream(&ftpSocket) << "PASS yourPassword\r\n";
ftpSocket.waitForReadyRead(30000);
// 创建多级目录
createDirectory(ftpSocket, directoryPath);
ftpSocket.close();
}
void FtpUploader::createDirectory(QTcpSocket& socket, const QString& fullPath)
{
QTextStream ftpStream(&socket);
QStringList directories = fullPath.split("/", QString::SkipEmptyParts);
QString currentPath;
for (const QString& dir : directories)
{
currentPath += "/" + dir;
// 尝试改变到目标目录
ftpStream << "CWD " << currentPath << "\r\n";
ftpStream.flush();
socket.waitForReadyRead();
QString response = ftpStream.readLine();
qDebug() << "CWD Response:" << response;
// 如果改变目录失败,尝试创建目录
if (response.startsWith("331"))
{
ftpStream << "MKD " << currentPath << "\r\n";
ftpStream.flush();
socket.waitForReadyRead();
qDebug() << "MKD Response:" << ftpStream.readLine();
// 尝试改变到目标目录
ftpStream << "CWD " << currentPath << "\r\n";
ftpStream.flush();
socket.waitForReadyRead();
qDebug() << "CWD Response:" << ftpStream.readLine();
}
if (response.startsWith("550")|| response.startsWith("257")|| response.startsWith("250"))
{
ftpStream << "MKD " << currentPath << "\r\n";
ftpStream.flush();
socket.waitForReadyRead();
qDebug() << "MKD Response:" << ftpStream.readLine();
}
}
// 最后,返回到根目录或初始目录
ftpStream << "CWD /\r\n";
ftpStream.flush();
socket.waitForReadyRead();
qDebug() << "Return to root Response:" << ftpStream.readLine();
}
替换 "yourIP", "yourUsername", "yourPassword"为你的 FTP 服务器信息。
调用FtpUploader
:
我在界面中创建了一个按钮,然后点击按钮出现对话框选择目录,点击确定后进行文件传输。
//在.h文件中声明变量
FtpUploader *m_pUploader;
//在.cpp构造函数中做如下声明:
m_pUploader = new FtpUploader("ftp://172.20.46.19", "username", "password");
void TFormConclusion::on_pBtnUpload_clicked()
{
// 打开文件对话框,选择目录
QString dirPath = QFileDialog::getExistingDirectory(this, "选择目录", QString(), QFileDialog::ShowDirsOnly);
if (!dirPath.isEmpty()) {
// 文件上传
bool bRet = m_pUploader->uploadDirectory(dirPath);
if (bRet)
{
QMessageBox::information(this,
QString("提示"),
QString("文件上传成功!"));
}
else
{
QMessageBox::information(this,
QString("提示"),
QString("文件上传失败!"));
}
}
}
这段代码提供了一个FtpUploader
类,它可以遍历指定文件夹并在FTP服务器上创建文件夹,然后上传其中的文件到FTP服务器。另外,关于文件的覆盖或重命名,这通常取决于服务器的配置和行为。可以在上传前检查服务器上是否存在同名文件,并据此决定是覆盖还是重命名本地文件。
这个示例是一个基础版本,可能需要根据具体需求进行调整。例如,处理网络错误、上传进度反馈、异步上传、大文件处理等方面都可能需要额外的代码。此外,由于Qt 5.15之后FTP支持已从Qt中移除,如果您使用的是较新的Qt版本,可能需要使用第三方库如QFtp或自行实现FTP协议。