基于Qt的文件传输系统

基于Qt的文件传输系统,包含服务器端和客户端的实现,支持断点续传、多文件传输、进度显示等功能。

一、系统架构

复制代码
文件传输系统架构:
├── 服务器端 (FileServer)
│   ├── 监听端口
│   ├── 接受客户端连接
│   ├── 接收文件
│   ├── 保存文件
│   └── 显示传输状态
├── 客户端 (FileClient)
│   ├── 连接服务器
│   ├── 选择文件
│   ├── 发送文件
│   ├── 支持断点续传
│   └── 显示传输进度
└── 公共模块
    ├── 文件信息结构体
    ├── 传输协议定义
    └── 工具函数

二、核心代码实现

2.1 传输协议定义 (transmissionprotocol.h)

cpp 复制代码
#ifndef TRANSMISSIONPROTOCOL_H
#define TRANSMISSIONPROTOCOL_H

#include <QObject>
#include <QString>
#include <QFileInfo>
#include <QDateTime>

// 传输命令
enum TransmissionCommand {
    CMD_FILE_INFO = 0x01,     // 文件信息
    CMD_FILE_DATA = 0x02,     // 文件数据
    CMD_TRANSFER_COMPLETE = 0x03, // 传输完成
    CMD_TRANSFER_CANCEL = 0x04,   // 取消传输
    CMD_HEARTBEAT = 0x05,     // 心跳包
    CMD_RESUME_REQUEST = 0x06, // 断点续传请求
    CMD_RESUME_RESPONSE = 0x07 // 断点续传响应
};

// 传输状态
enum TransmissionStatus {
    STATUS_IDLE = 0,
    STATUS_CONNECTING,
    STATUS_TRANSFERRING,
    STATUS_PAUSED,
    STATUS_COMPLETED,
    STATUS_CANCELLED,
    STATUS_ERROR
};

// 文件信息结构
struct FileInfo {
    QString fileName;         // 文件名
    qint64 fileSize;         // 文件大小
    QString fileHash;        // 文件哈希(MD5)
    QDateTime modifyTime;    // 修改时间
    qint64 transferredSize;  // 已传输大小
    bool resumeSupported;    // 是否支持断点续传
};

// 传输块信息
struct FileBlock {
    qint64 blockIndex;       // 块索引
    qint64 blockOffset;      // 块偏移
    qint64 blockSize;        // 块大小
    QByteArray blockData;    // 块数据
    QString blockHash;       // 块哈希
};

// 传输协议常量
const int DEFAULT_PORT = 8888;
const int MAX_BLOCK_SIZE = 64 * 1024;  // 64KB
const int HEADER_SIZE = 24;           // 头部大小
const int TIMEOUT_MS = 30000;         // 超时时间(毫秒)
const QString SERVER_NAME = "QtFileTransferServer";

#endif // TRANSMISSIONPROTOCOL_H

2.2 服务器端实现 (fileserver.hfileserver.cpp)

fileserver.h
cpp 复制代码
#ifndef FILESERVER_H
#define FILESERVER_H

#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QFile>
#include <QMap>
#include <QTimer>
#include "transmissionprotocol.h"

class FileServer : public QObject
{
    Q_OBJECT

public:
    explicit FileServer(QObject *parent = nullptr);
    ~FileServer();

    bool startServer(quint16 port = DEFAULT_PORT);
    void stopServer();
    bool isRunning() const;
    QString getServerAddress() const;
    quint16 getServerPort() const;
    int getConnectedClients() const;
    QString getSavePath() const;
    void setSavePath(const QString &path);

signals:
    void serverStarted(const QString &address, quint16 port);
    void serverStopped();
    void clientConnected(const QString &clientAddress, quint16 clientPort);
    void clientDisconnected(const QString &clientAddress);
    void fileTransferStarted(const QString &fileName, qint64 fileSize);
    void fileTransferProgress(const QString &fileName, qint64 transferred, qint64 total);
    void fileTransferCompleted(const QString &fileName, const QString &savePath);
    void fileTransferCancelled(const QString &fileName);
    void errorOccurred(const QString &error);

private slots:
    void onNewConnection();
    void onClientReadyRead();
    void onClientDisconnected();
    void onHeartbeatTimeout();
    void onSocketError(QAbstractSocket::SocketError error);

private:
    void processCommand(QTcpSocket *socket, const QByteArray &data);
    void handleFileInfo(QTcpSocket *socket, const QByteArray &data);
    void handleFileData(QTcpSocket *socket, const QByteArray &data);
    void handleResumeRequest(QTcpSocket *socket, const QByteArray &data);
    void saveFileBlock(QTcpSocket *socket, const FileBlock &block);
    void sendCommand(QTcpSocket *socket, TransmissionCommand cmd, const QByteArray &data = QByteArray());
    void cleanupClient(QTcpSocket *socket);

private:
    QTcpServer *tcpServer;
    QMap<QTcpSocket*, FileInfo> activeTransfers;  // 正在传输的文件信息
    QMap<QTcpSocket*, QFile*> files;              // 打开的文件
    QMap<QTcpSocket*, qint64> receivedBytes;       // 已接收字节数
    QMap<QTcpSocket*, QTimer*> heartbeats;        // 心跳计时器
    QString savePath;                             // 文件保存路径
    bool running;
};

#endif // FILESERVER_H
fileserver.cpp
cpp 复制代码
#include "fileserver.h"
#include <QHostAddress>
#include <QDataStream>
#include <QCryptographicHash>
#include <QDir>
#include <QFileInfo>
#include <QDateTime>

FileServer::FileServer(QObject *parent) : QObject(parent)
{
    tcpServer = new QTcpServer(this);
    running = false;
    savePath = QDir::currentPath() + "/received_files";
    
    // 确保保存目录存在
    QDir dir(savePath);
    if (!dir.exists()) {
        dir.mkpath(".");
    }
    
    connect(tcpServer, &QTcpServer::newConnection, this, &FileServer::onNewConnection);
}

FileServer::~FileServer()
{
    stopServer();
}

bool FileServer::startServer(quint16 port)
{
    if (running) {
        emit errorOccurred("Server is already running");
        return false;
    }
    
    if (!tcpServer->listen(QHostAddress::Any, port)) {
        emit errorOccurred(QString("Failed to start server: %1").arg(tcpServer->errorString()));
        return false;
    }
    
    running = true;
    emit serverStarted(getServerAddress(), getServerPort());
    return true;
}

void FileServer::stopServer()
{
    if (!running) return;
    
    // 断开所有客户端
    foreach (QTcpSocket *socket, activeTransfers.keys()) {
        cleanupClient(socket);
    }
    
    tcpServer->close();
    running = false;
    emit serverStopped();
}

bool FileServer::isRunning() const
{
    return running;
}

QString FileServer::getServerAddress() const
{
    return tcpServer->serverAddress().toString();
}

quint16 FileServer::getServerPort() const
{
    return tcpServer->serverPort();
}

int FileServer::getConnectedClients() const
{
    return activeTransfers.size();
}

QString FileServer::getSavePath() const
{
    return savePath;
}

void FileServer::setSavePath(const QString &path)
{
    savePath = path;
    QDir dir(savePath);
    if (!dir.exists()) {
        dir.mkpath(".");
    }
}

void FileServer::onNewConnection()
{
    QTcpSocket *socket = tcpServer->nextPendingConnection();
    
    connect(socket, &QTcpSocket::readyRead, this, &FileServer::onClientReadyRead);
    connect(socket, &QTcpSocket::disconnected, this, &FileServer::onClientDisconnected);
    connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error),
            this, &FileServer::onSocketError);
    
    // 设置心跳计时器
    QTimer *heartbeat = new QTimer(socket);
    heartbeat->setInterval(TIMEOUT_MS);
    connect(heartbeat, &QTimer::timeout, this, &FileServer::onHeartbeatTimeout);
    heartbeats[socket] = heartbeat;
    heartbeat->start();
    
    emit clientConnected(socket->peerAddress().toString(), socket->peerPort());
}

void FileServer::onClientReadyRead()
{
    QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
    if (!socket) return;
    
    // 重置心跳计时器
    if (heartbeats.contains(socket)) {
        heartbeats[socket]->start();
    }
    
    // 读取数据
    QByteArray data = socket->readAll();
    if (data.isEmpty()) return;
    
    processCommand(socket, data);
}

void FileServer::processCommand(QTcpSocket *socket, const QByteArray &data)
{
    if (data.size() < 1) return;
    
    TransmissionCommand cmd = static_cast<TransmissionCommand>(data[0]);
    QByteArray payload = data.mid(1);
    
    switch (cmd) {
        case CMD_FILE_INFO:
            handleFileInfo(socket, payload);
            break;
        case CMD_FILE_DATA:
            handleFileData(socket, payload);
            break;
        case CMD_RESUME_REQUEST:
            handleResumeRequest(socket, payload);
            break;
        case CMD_HEARTBEAT:
            // 心跳包,只需重置计时器
            break;
        default:
            emit errorOccurred(QString("Unknown command: %1").arg(cmd));
            break;
    }
}

void FileServer::handleFileInfo(QTcpSocket *socket, const QByteArray &data)
{
    QDataStream stream(data);
    stream.setVersion(QDataStream::Qt_5_15);
    
    FileInfo fileInfo;
    stream >> fileInfo.fileName >> fileInfo.fileSize >> fileInfo.fileHash 
           >> fileInfo.modifyTime >> fileInfo.resumeSupported;
    
    // 检查文件是否已存在
    QString saveFilePath = savePath + "/" + fileInfo.fileName;
    QFileInfo existingFile(saveFilePath);
    
    if (existingFile.exists() && existingFile.size() == fileInfo.fileSize) {
        // 文件已存在且大小相同,可能是重复传输
        emit errorOccurred(QString("File already exists: %1").arg(fileInfo.fileName));
        sendCommand(socket, CMD_TRANSFER_CANCEL);
        return;
    }
    
    // 创建文件
    QFile *file = new QFile(saveFilePath, socket);
    if (!file->open(QIODevice::WriteOnly)) {
        emit errorOccurred(QString("Failed to create file: %1").arg(saveFilePath));
        sendCommand(socket, CMD_TRANSFER_CANCEL);
        return;
    }
    
    // 保存文件信息
    activeTransfers[socket] = fileInfo;
    files[socket] = file;
    receivedBytes[socket] = 0;
    
    emit fileTransferStarted(fileInfo.fileName, fileInfo.fileSize);
}

void FileServer::handleFileData(QTcpSocket *socket, const QByteArray &data)
{
    if (!activeTransfers.contains(socket) || !files.contains(socket)) {
        emit errorOccurred("No active transfer for this socket");
        return;
    }
    
    QFile *file = files[socket];
    FileInfo &fileInfo = activeTransfers[socket];
    
    // 写入文件
    qint64 bytesWritten = file->write(data);
    if (bytesWritten != data.size()) {
        emit errorOccurred(QString("Failed to write file data: %1").arg(file->errorString()));
        sendCommand(socket, CMD_TRANSFER_CANCEL);
        return;
    }
    
    // 更新接收字节数
    receivedBytes[socket] += bytesWritten;
    
    // 发送进度信号
    emit fileTransferProgress(fileInfo.fileName, receivedBytes[socket], fileInfo.fileSize);
    
    // 检查是否完成
    if (receivedBytes[socket] >= fileInfo.fileSize) {
        file->close();
        
        // 验证文件哈希
        file->open(QIODevice::ReadOnly);
        QByteArray fileHash = QCryptographicHash::hash(file->readAll(), QCryptographicHash::Md5).toHex();
        file->close();
        
        if (fileHash != fileInfo.fileHash.toLatin1()) {
            emit errorOccurred(QString("File hash verification failed for %1").arg(fileInfo.fileName));
            sendCommand(socket, CMD_TRANSFER_CANCEL);
        } else {
            emit fileTransferCompleted(fileInfo.fileName, file->fileName());
            sendCommand(socket, CMD_TRANSFER_COMPLETE);
        }
        
        cleanupClient(socket);
    }
}

void FileServer::handleResumeRequest(QTcpSocket *socket, const QByteArray &data)
{
    QDataStream stream(data);
    stream.setVersion(QDataStream::Qt_5_15);
    
    QString fileName;
    stream >> fileName;
    
    QString filePath = savePath + "/" + fileName;
    QFileInfo fileInfo(filePath);
    
    if (fileInfo.exists()) {
        qint64 fileSize = fileInfo.size();
        QByteArray response;
        QDataStream responseStream(&response, QIODevice::WriteOnly);
        responseStream.setVersion(QDataStream::Qt_5_15);
        responseStream << fileSize;
        
        sendCommand(socket, CMD_RESUME_RESPONSE, response);
    } else {
        // 文件不存在,从头开始传输
        QByteArray response;
        QDataStream responseStream(&response, QIODevice::WriteOnly);
        responseStream.setVersion(QDataStream::Qt_5_15);
        responseStream << static_cast<qint64>(-1);
        
        sendCommand(socket, CMD_RESUME_RESPONSE, response);
    }
}

void FileServer::sendCommand(QTcpSocket *socket, TransmissionCommand cmd, const QByteArray &data)
{
    QByteArray packet;
    packet.append(static_cast<char>(cmd));
    packet.append(data);
    socket->write(packet);
}

void FileServer::cleanupClient(QTcpSocket *socket)
{
    if (files.contains(socket)) {
        QFile *file = files[socket];
        if (file->isOpen()) {
            file->close();
        }
        delete file;
        files.remove(socket);
    }
    
    if (heartbeats.contains(socket)) {
        QTimer *timer = heartbeats[socket];
        timer->stop();
        delete timer;
        heartbeats.remove(socket);
    }
    
    activeTransfers.remove(socket);
    receivedBytes.remove(socket);
    
    socket->deleteLater();
}

void FileServer::onClientDisconnected()
{
    QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
    if (!socket) return;
    
    emit clientDisconnected(socket->peerAddress().toString());
    cleanupClient(socket);
}

void FileServer::onHeartbeatTimeout()
{
    QTimer *timer = qobject_cast<QTimer*>(sender());
    if (!timer) return;
    
    // 找到对应的socket
    QTcpSocket *socket = nullptr;
    foreach (QTcpSocket *s, heartbeats.keys()) {
        if (heartbeats[s] == timer) {
            socket = s;
            break;
        }
    }
    
    if (socket) {
        emit errorOccurred(QString("Client %1:%2 disconnected due to timeout")
                          .arg(socket->peerAddress().toString())
                          .arg(socket->peerPort()));
        cleanupClient(socket);
    }
}

void FileServer::onSocketError(QAbstractSocket::SocketError error)
{
    QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
    if (!socket) return;
    
    emit errorOccurred(QString("Socket error %1: %2")
                      .arg(error)
                      .arg(socket->errorString()));
    
    cleanupClient(socket);
}

2.3 客户端实现 (fileclient.hfileclient.cpp)

fileclient.h
cpp 复制代码
#ifndef FILECLIENT_H
#define FILECLIENT_H

#include <QObject>
#include <QTcpSocket>
#include <QFile>
#include <QTimer>
#include "transmissionprotocol.h"

class FileClient : public QObject
{
    Q_OBJECT

public:
    explicit FileClient(QObject *parent = nullptr);
    ~FileClient();

    bool connectToServer(const QString &host, quint16 port = DEFAULT_PORT);
    void disconnectFromServer();
    bool sendFile(const QString &filePath, bool resume = false);
    void cancelTransfer();
    bool isConnected() const;
    bool isTransferring() const;
    QString getServerAddress() const;
    quint16 getServerPort() const;
    qint64 getTransferredSize() const;
    qint64 getFileSize() const;
    TransmissionStatus getStatus() const;

signals:
    void connected();
    void disconnected();
    void transferStarted(const QString &fileName, qint64 fileSize);
    void transferProgress(qint64 transferred, qint64 total);
    void transferCompleted(const QString &fileName);
    void transferCancelled(const QString &fileName);
    void errorOccurred(const QString &error);

private slots:
    void onConnected();
    void onDisconnected();
    void onReadyRead();
    void onSendNextBlock();
    void onHeartbeatTimeout();
    void onSocketError(QAbstractSocket::SocketError error);

private:
    void sendFileInfo();
    void sendFileData();
    void sendResumeRequest();
    void processCommand(TransmissionCommand cmd, const QByteArray &data);
    void cleanupTransfer();

private:
    QTcpSocket *tcpSocket;
    QFile *file;
    FileInfo fileInfo;
    QTimer *heartbeatTimer;
    QTimer *sendTimer;
    qint64 currentBlockIndex;
    qint64 totalBlocks;
    TransmissionStatus status;
    bool resumeTransfer;
};

#endif // FILECLIENT_H
fileclient.cpp
cpp 复制代码
#include "fileclient.h"
#include <QHostAddress>
#include <QDataStream>
#include <QCryptographicHash>
#include <QFileInfo>
#include <QDateTime>

FileClient::FileClient(QObject *parent) : QObject(parent)
{
    tcpSocket = new QTcpSocket(this);
    file = nullptr;
    heartbeatTimer = new QTimer(this);
    sendTimer = new QTimer(this);
    status = STATUS_IDLE;
    resumeTransfer = false;
    
    connect(tcpSocket, &QTcpSocket::connected, this, &FileClient::onConnected);
    connect(tcpSocket, &QTcpSocket::disconnected, this, &FileClient::onDisconnected);
    connect(tcpSocket, &QTcpSocket::readyRead, this, &FileClient::onReadyRead);
    connect(tcpSocket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error),
            this, &FileClient::onSocketError);
    
    connect(heartbeatTimer, &QTimer::timeout, this, &FileClient::onHeartbeatTimeout);
    connect(sendTimer, &QTimer::timeout, this, &FileClient::onSendNextBlock);
    
    heartbeatTimer->setInterval(TIMEOUT_MS / 2);
    sendTimer->setInterval(10);  // 发送间隔,控制速度
}

FileClient::~FileClient()
{
    cancelTransfer();
    disconnectFromServer();
}

bool FileClient::connectToServer(const QString &host, quint16 port)
{
    if (isConnected()) {
        emit errorOccurred("Already connected to server");
        return false;
    }
    
    status = STATUS_CONNECTING;
    tcpSocket->connectToHost(host, port);
    
    if (!tcpSocket->waitForConnected(5000)) {
        status = STATUS_ERROR;
        emit errorOccurred(QString("Failed to connect to server: %1").arg(tcpSocket->errorString()));
        return false;
    }
    
    return true;
}

void FileClient::disconnectFromServer()
{
    if (isConnected()) {
        tcpSocket->disconnectFromHost();
    }
}

bool FileClient::sendFile(const QString &filePath, bool resume)
{
    if (!isConnected()) {
        emit errorOccurred("Not connected to server");
        return false;
    }
    
    if (isTransferring()) {
        emit errorOccurred("Another transfer is in progress");
        return false;
    }
    
    // 打开文件
    file = new QFile(filePath, this);
    if (!file->open(QIODevice::ReadOnly)) {
        emit errorOccurred(QString("Failed to open file: %1").arg(file->errorString()));
        delete file;
        file = nullptr;
        return false;
    }
    
    // 获取文件信息
    QFileInfo info(filePath);
    fileInfo.fileName = info.fileName();
    fileInfo.fileSize = info.size();
    fileInfo.modifyTime = info.lastModified();
    fileInfo.resumeSupported = resume;
    
    // 计算文件哈希
    file->seek(0);
    QByteArray fileData = file->readAll();
    fileInfo.fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5).toHex();
    file->seek(0);
    
    // 计算总块数
    totalBlocks = (fileInfo.fileSize + MAX_BLOCK_SIZE - 1) / MAX_BLOCK_SIZE;
    currentBlockIndex = 0;
    
    // 设置状态
    status = STATUS_TRANSFERRING;
    resumeTransfer = resume;
    
    // 开始传输
    if (resume) {
        sendResumeRequest();
    } else {
        sendFileInfo();
    }
    
    emit transferStarted(fileInfo.fileName, fileInfo.fileSize);
    heartbeatTimer->start();
    
    return true;
}

void FileClient::cancelTransfer()
{
    if (isTransferring()) {
        status = STATUS_CANCELLED;
        sendTimer->stop();
        heartbeatTimer->stop();
        
        if (file) {
            file->close();
            delete file;
            file = nullptr;
        }
        
        emit transferCancelled(fileInfo.fileName);
        cleanupTransfer();
    }
}

bool FileClient::isConnected() const
{
    return tcpSocket->state() == QAbstractSocket::ConnectedState;
}

bool FileClient::isTransferring() const
{
    return status == STATUS_TRANSFERRING;
}

QString FileClient::getServerAddress() const
{
    return tcpSocket->peerAddress().toString();
}

quint16 FileClient::getServerPort() const
{
    return tcpSocket->peerPort();
}

qint64 FileClient::getTransferredSize() const
{
    return currentBlockIndex * MAX_BLOCK_SIZE;
}

qint64 FileClient::getFileSize() const
{
    return fileInfo.fileSize;
}

TransmissionStatus FileClient::getStatus() const
{
    return status;
}

void FileClient::onConnected()
{
    status = STATUS_IDLE;
    emit connected();
}

void FileClient::onDisconnected()
{
    status = STATUS_IDLE;
    heartbeatTimer->stop();
    sendTimer->stop();
    cleanupTransfer();
    emit disconnected();
}

void FileClient::onReadyRead()
{
    QByteArray data = tcpSocket->readAll();
    if (data.isEmpty()) return;
    
    TransmissionCommand cmd = static_cast<TransmissionCommand>(data[0]);
    QByteArray payload = data.mid(1);
    
    processCommand(cmd, payload);
}

void FileClient::processCommand(TransmissionCommand cmd, const QByteArray &data)
{
    switch (cmd) {
        case CMD_FILE_INFO:
            // 服务器确认文件信息,开始发送数据
            sendTimer->start();
            break;
            
        case CMD_RESUME_RESPONSE:
            // 断点续传响应
            {
                QDataStream stream(data);
                stream.setVersion(QDataStream::Qt_5_15);
                qint64 receivedSize;
                stream >> receivedSize;
                
                if (receivedSize >= 0) {
                    // 从指定位置继续传输
                    currentBlockIndex = receivedSize / MAX_BLOCK_SIZE;
                    file->seek(receivedSize);
                    sendTimer->start();
                } else {
                    // 文件不存在,从头开始
                    sendFileInfo();
                }
            }
            break;
            
        case CMD_TRANSFER_COMPLETE:
            // 传输完成
            status = STATUS_COMPLETED;
            sendTimer->stop();
            heartbeatTimer->stop();
            emit transferCompleted(fileInfo.fileName);
            cleanupTransfer();
            break;
            
        case CMD_TRANSFER_CANCEL:
            // 传输被取消
            status = STATUS_CANCELLED;
            sendTimer->stop();
            heartbeatTimer->stop();
            emit transferCancelled(fileInfo.fileName);
            cleanupTransfer();
            break;
            
        case CMD_HEARTBEAT:
            // 心跳包,重置计时器
            break;
            
        default:
            emit errorOccurred(QString("Unknown command from server: %1").arg(cmd));
            break;
    }
}

void FileClient::sendFileInfo()
{
    QByteArray packet;
    QDataStream stream(&packet, QIODevice::WriteOnly);
    stream.setVersion(QDataStream::Qt_5_15);
    
    stream << fileInfo.fileName << fileInfo.fileSize << fileInfo.fileHash
           << fileInfo.modifyTime << fileInfo.resumeSupported;
    
    // 发送命令
    QByteArray command;
    command.append(static_cast<char>(CMD_FILE_INFO));
    command.append(packet);
    tcpSocket->write(command);
}

void FileClient::sendResumeRequest()
{
    QByteArray packet;
    QDataStream stream(&packet, QIODevice::WriteOnly);
    stream.setVersion(QDataStream::Qt_5_15);
    
    stream << fileInfo.fileName;
    
    QByteArray command;
    command.append(static_cast<char>(CMD_RESUME_REQUEST));
    command.append(packet);
    tcpSocket->write(command);
}

void FileClient::sendFileData()
{
    if (!file || !file->isOpen()) {
        cancelTransfer();
        return;
    }
    
    if (currentBlockIndex >= totalBlocks) {
        sendTimer->stop();
        return;
    }
    
    // 读取一块数据
    qint64 blockSize = MAX_BLOCK_SIZE;
    if (currentBlockIndex == totalBlocks - 1) {
        blockSize = fileInfo.fileSize - currentBlockIndex * MAX_BLOCK_SIZE;
    }
    
    QByteArray blockData = file->read(blockSize);
    if (blockData.size() != blockSize) {
        emit errorOccurred("Failed to read file data");
        cancelTransfer();
        return;
    }
    
    // 发送数据块
    QByteArray packet;
    QDataStream stream(&packet, QIODevice::WriteOnly);
    stream.setVersion(QDataStream::Qt_5_15);
    
    stream << currentBlockIndex << currentBlockIndex * MAX_BLOCK_SIZE << blockSize << blockData;
    
    QByteArray command;
    command.append(static_cast<char>(CMD_FILE_DATA));
    command.append(packet);
    tcpSocket->write(command);
    
    // 更新进度
    currentBlockIndex++;
    qint64 transferred = currentBlockIndex * MAX_BLOCK_SIZE;
    if (transferred > fileInfo.fileSize) {
        transferred = fileInfo.fileSize;
    }
    
    emit transferProgress(transferred, fileInfo.fileSize);
    
    // 如果这是最后一块,停止发送定时器
    if (currentBlockIndex >= totalBlocks) {
        sendTimer->stop();
    }
}

void FileClient::onSendNextBlock()
{
    if (status == STATUS_TRANSFERRING) {
        sendFileData();
    }
}

void FileClient::onHeartbeatTimeout()
{
    // 发送心跳包
    QByteArray command;
    command.append(static_cast<char>(CMD_HEARTBEAT));
    tcpSocket->write(command);
}

void FileClient::onSocketError(QAbstractSocket::SocketError error)
{
    status = STATUS_ERROR;
    emit errorOccurred(QString("Socket error %1: %2").arg(error).arg(tcpSocket->errorString()));
    cleanupTransfer();
}

void FileClient::cleanupTransfer()
{
    if (file) {
        file->close();
        delete file;
        file = nullptr;
    }
    
    fileInfo = FileInfo();
    currentBlockIndex = 0;
    totalBlocks = 0;
    status = STATUS_IDLE;
}

2.4 主窗口界面 (mainwindow.hmainwindow.cpp)

mainwindow.h
cpp 复制代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QFileDialog>
#include <QMessageBox>
#include <QProgressBar>
#include <QTableWidget>
#include <QTableWidgetItem>
#include "fileserver.h"
#include "fileclient.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    // 服务器相关
    void on_btnStartServer_clicked();
    void on_btnStopServer_clicked();
    void on_btnBrowseSavePath_clicked();
    
    // 客户端相关
    void on_btnConnect_clicked();
    void on_btnDisconnect_clicked();
    void on_btnBrowseFile_clicked();
    void on_btnSendFile_clicked();
    void on_btnCancelTransfer_clicked();
    void on_chkResume_toggled(bool checked);
    
    // 服务器信号
    void onServerStarted(const QString &address, quint16 port);
    void onServerStopped();
    void onClientConnected(const QString &clientAddress, quint16 clientPort);
    void onClientDisconnected(const QString &clientAddress);
    void onFileTransferStarted(const QString &fileName, qint64 fileSize);
    void onFileTransferProgress(const QString &fileName, qint64 transferred, qint64 total);
    void onFileTransferCompleted(const QString &fileName, const QString &savePath);
    void onFileTransferCancelled(const QString &fileName);
    void onServerError(const QString &error);
    
    // 客户端信号
    void onClientConnected();
    void onClientDisconnected();
    void onTransferStarted(const QString &fileName, qint64 fileSize);
    void onTransferProgress(qint64 transferred, qint64 total);
    void onTransferCompleted(const QString &fileName);
    void onTransferCancelled(const QString &fileName);
    void onClientError(const QString &error);

private:
    Ui::MainWindow *ui;
    FileServer *server;
    FileClient *client;
    QMap<QString, QProgressBar*> progressBars;  // 存储进度条
    
    void updateServerStatus(bool running);
    void updateClientStatus(bool connected);
    void addTransferToTable(const QString &fileName, qint64 fileSize, bool isServer);
    void updateTransferProgress(const QString &fileName, qint64 transferred, qint64 total);
    void removeTransferFromTable(const QString &fileName);
};

#endif // MAINWINDOW_H
mainwindow.cpp
cpp 复制代码
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    
    // 设置窗口标题
    setWindowTitle("Qt File Transfer System");
    
    // 初始化服务器
    server = new FileServer(this);
    connect(server, &FileServer::serverStarted, this, &MainWindow::onServerStarted);
    connect(server, &FileServer::serverStopped, this, &MainWindow::onServerStopped);
    connect(server, &FileServer::clientConnected, this, &MainWindow::onClientConnected);
    connect(server, &FileServer::clientDisconnected, this, &MainWindow::onClientDisconnected);
    connect(server, &FileServer::fileTransferStarted, this, &MainWindow::onFileTransferStarted);
    connect(server, &FileServer::fileTransferProgress, this, &MainWindow::onFileTransferProgress);
    connect(server, &FileServer::fileTransferCompleted, this, &MainWindow::onFileTransferCompleted);
    connect(server, &FileServer::fileTransferCancelled, this, &MainWindow::onFileTransferCancelled);
    connect(server, &FileServer::errorOccurred, this, &MainWindow::onServerError);
    
    // 初始化客户端
    client = new FileClient(this);
    connect(client, &FileClient::connected, this, &MainWindow::onClientConnected);
    connect(client, &FileClient::disconnected, this, &MainWindow::onClientDisconnected);
    connect(client, &FileClient::transferStarted, this, &MainWindow::onTransferStarted);
    connect(client, &FileClient::transferProgress, this, &MainWindow::onTransferProgress);
    connect(client, &FileClient::transferCompleted, this, &MainWindow::onTransferCompleted);
    connect(client, &FileClient::transferCancelled, this, &MainWindow::onTransferCancelled);
    connect(client, &FileClient::errorOccurred, this, &MainWindow::onClientError);
    
    // 设置默认值
    ui->txtServerPort->setText(QString::number(DEFAULT_PORT));
    ui->txtServerSavePath->setText(QDir::currentPath() + "/received_files");
    ui->txtServerAddress->setText("127.0.0.1");
    ui->txtServerPortClient->setText(QString::number(DEFAULT_PORT));
    ui->chkResume->setChecked(true);
    
    // 设置表格
    ui->tblTransfers->setColumnCount(5);
    ui->tblTransfers->setHorizontalHeaderLabels({"File Name", "Size", "Progress", "Status", "Actions"});
    ui->tblTransfers->horizontalHeader()->setStretchLastSection(true);
    ui->tblTransfers->setSelectionBehavior(QAbstractItemView::SelectRows);
    ui->tblTransfers->setEditTriggers(QAbstractItemView::NoEditTriggers);
    
    updateServerStatus(false);
    updateClientStatus(false);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_btnStartServer_clicked()
{
    quint16 port = ui->txtServerPort->text().toUShort();
    if (port == 0) {
        QMessageBox::warning(this, "Warning", "Invalid port number");
        return;
    }
    
    if (server->startServer(port)) {
        updateServerStatus(true);
    }
}

void MainWindow::on_btnStopServer_clicked()
{
    server->stopServer();
    updateServerStatus(false);
}

void MainWindow::on_btnBrowseSavePath_clicked()
{
    QString dir = QFileDialog::getExistingDirectory(this, "Select Save Directory", 
                                                   ui->txtServerSavePath->text());
    if (!dir.isEmpty()) {
        ui->txtServerSavePath->setText(dir);
        server->setSavePath(dir);
    }
}

void MainWindow::on_btnConnect_clicked()
{
    QString address = ui->txtServerAddress->text();
    quint16 port = ui->txtServerPortClient->text().toUShort();
    
    if (address.isEmpty() || port == 0) {
        QMessageBox::warning(this, "Warning", "Please enter server address and port");
        return;
    }
    
    if (client->connectToServer(address, port)) {
        updateClientStatus(true);
    }
}

void MainWindow::on_btnDisconnect_clicked()
{
    client->disconnectFromServer();
    updateClientStatus(false);
}

void MainWindow::on_btnBrowseFile_clicked()
{
    QString fileName = QFileDialog::getOpenFileName(this, "Select File to Send");
    if (!fileName.isEmpty()) {
        ui->txtFilePath->setText(fileName);
    }
}

void MainWindow::on_btnSendFile_clicked()
{
    QString filePath = ui->txtFilePath->text();
    if (filePath.isEmpty()) {
        QMessageBox::warning(this, "Warning", "Please select a file to send");
        return;
    }
    
    if (!client->isConnected()) {
        QMessageBox::warning(this, "Warning", "Not connected to server");
        return;
    }
    
    bool resume = ui->chkResume->isChecked();
    client->sendFile(filePath, resume);
}

void MainWindow::on_btnCancelTransfer_clicked()
{
    client->cancelTransfer();
}

void MainWindow::on_chkResume_toggled(bool checked)
{
    // 断点续传选项
    Q_UNUSED(checked);
}

void MainWindow::onServerStarted(const QString &address, quint16 port)
{
    ui->lblServerStatus->setText(QString("Running: %1:%2").arg(address).arg(port));
    ui->btnStartServer->setEnabled(false);
    ui->btnStopServer->setEnabled(true);
    ui->txtServerPort->setEnabled(false);
}

void MainWindow::onServerStopped()
{
    ui->lblServerStatus->setText("Stopped");
    ui->btnStartServer->setEnabled(true);
    ui->btnStopServer->setEnabled(false);
    ui->txtServerPort->setEnabled(true);
}

void MainWindow::onClientConnected(const QString &clientAddress, quint16 clientPort)
{
    ui->txtServerLog->append(QString("[%1] Client connected: %2:%3")
                            .arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
                            .arg(clientAddress)
                            .arg(clientPort));
}

void MainWindow::onClientDisconnected(const QString &clientAddress)
{
    ui->txtServerLog->append(QString("[%1] Client disconnected: %2")
                            .arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
                            .arg(clientAddress));
}

void MainWindow::onFileTransferStarted(const QString &fileName, qint64 fileSize)
{
    ui->txtServerLog->append(QString("[%1] File transfer started: %2 (%3 bytes)")
                            .arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
                            .arg(fileName)
                            .arg(fileSize));
    
    addTransferToTable(fileName, fileSize, true);
}

void MainWindow::onFileTransferProgress(const QString &fileName, qint64 transferred, qint64 total)
{
    updateTransferProgress(fileName, transferred, total);
}

void MainWindow::onFileTransferCompleted(const QString &fileName, const QString &savePath)
{
    ui->txtServerLog->append(QString("[%1] File transfer completed: %2 saved to %3")
                            .arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
                            .arg(fileName)
                            .arg(savePath));
    
    removeTransferFromTable(fileName);
}

void MainWindow::onFileTransferCancelled(const QString &fileName)
{
    ui->txtServerLog->append(QString("[%1] File transfer cancelled: %2")
                            .arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
                            .arg(fileName));
    
    removeTransferFromTable(fileName);
}

void MainWindow::onServerError(const QString &error)
{
    ui->txtServerLog->append(QString("[%1] ERROR: %2")
                            .arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
                            .arg(error));
}

void MainWindow::onClientConnected()
{
    ui->lblClientStatus->setText("Connected");
    ui->btnConnect->setEnabled(false);
    ui->btnDisconnect->setEnabled(true);
    ui->btnSendFile->setEnabled(true);
    ui->btnCancelTransfer->setEnabled(true);
}

void MainWindow::onClientDisconnected()
{
    ui->lblClientStatus->setText("Disconnected");
    ui->btnConnect->setEnabled(true);
    ui->btnDisconnect->setEnabled(false);
    ui->btnSendFile->setEnabled(false);
    ui->btnCancelTransfer->setEnabled(false);
}

void MainWindow::onTransferStarted(const QString &fileName, qint64 fileSize)
{
    ui->txtClientLog->append(QString("[%1] File transfer started: %2 (%3 bytes)")
                            .arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
                            .arg(fileName)
                            .arg(fileSize));
    
    addTransferToTable(fileName, fileSize, false);
}

void MainWindow::onTransferProgress(qint64 transferred, qint64 total)
{
    QString fileName = QFileInfo(ui->txtFilePath->text()).fileName();
    updateTransferProgress(fileName, transferred, total);
}

void MainWindow::onTransferCompleted(const QString &fileName)
{
    ui->txtClientLog->append(QString("[%1] File transfer completed: %2")
                            .arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
                            .arg(fileName));
    
    removeTransferFromTable(fileName);
}

void MainWindow::onTransferCancelled(const QString &fileName)
{
    ui->txtClientLog->append(QString("[%1] File transfer cancelled: %2")
                            .arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
                            .arg(fileName));
    
    removeTransferFromTable(fileName);
}

void MainWindow::onClientError(const QString &error)
{
    ui->txtClientLog->append(QString("[%1] ERROR: %2")
                            .arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
                            .arg(error));
}

void MainWindow::updateServerStatus(bool running)
{
    if (running) {
        ui->lblServerStatus->setStyleSheet("QLabel { color: green; font-weight: bold; }");
    } else {
        ui->lblServerStatus->setStyleSheet("QLabel { color: red; }");
    }
}

void MainWindow::updateClientStatus(bool connected)
{
    if (connected) {
        ui->lblClientStatus->setStyleSheet("QLabel { color: green; font-weight: bold; }");
    } else {
        ui->lblClientStatus->setStyleSheet("QLabel { color: red; }");
    }
}

void MainWindow::addTransferToTable(const QString &fileName, qint64 fileSize, bool isServer)
{
    int row = ui->tblTransfers->rowCount();
    ui->tblTransfers->insertRow(row);
    
    // 文件名
    ui->tblTransfers->setItem(row, 0, new QTableWidgetItem(fileName));
    
    // 文件大小
    QString sizeStr;
    if (fileSize < 1024) {
        sizeStr = QString("%1 B").arg(fileSize);
    } else if (fileSize < 1024 * 1024) {
        sizeStr = QString("%.1f KB").arg(fileSize / 1024.0);
    } else {
        sizeStr = QString("%.1f MB").arg(fileSize / (1024.0 * 1024.0));
    }
    ui->tblTransfers->setItem(row, 1, new QTableWidgetItem(sizeStr));
    
    // 进度条
    QProgressBar *progressBar = new QProgressBar();
    progressBar->setMinimum(0);
    progressBar->setMaximum(100);
    progressBar->setValue(0);
    progressBar->setTextVisible(true);
    progressBar->setFormat("%p%");
    ui->tblTransfers->setCellWidget(row, 2, progressBar);
    progressBars[fileName] = progressBar;
    
    // 状态
    QString status = isServer ? "Receiving" : "Sending";
    ui->tblTransfers->setItem(row, 3, new QTableWidgetItem(status));
    
    // 操作按钮
    QWidget *actionsWidget = new QWidget();
    QHBoxLayout *layout = new QHBoxLayout(actionsWidget);
    layout->setContentsMargins(0, 0, 0, 0);
    
    QPushButton *cancelBtn = new QPushButton("Cancel");
    cancelBtn->setMaximumWidth(60);
    connect(cancelBtn, &QPushButton::clicked,  {
        if (isServer) {
            // 服务器端取消传输
        } else {
            client->cancelTransfer();
        }
    });
    
    layout->addWidget(cancelBtn);
    ui->tblTransfers->setCellWidget(row, 4, actionsWidget);
}

void MainWindow::updateTransferProgress(const QString &fileName, qint64 transferred, qint64 total)
{
    if (progressBars.contains(fileName)) {
        QProgressBar *progressBar = progressBars[fileName];
        int percent = (total > 0) ? static_cast<int>((transferred * 100) / total) : 0;
        progressBar->setValue(percent);
        
        // 更新状态
        for (int row = 0; row < ui->tblTransfers->rowCount(); row++) {
            if (ui->tblTransfers->item(row, 0)->text() == fileName) {
                QString status = (percent < 100) ? "Transferring" : "Completed";
                ui->tblTransfers->setItem(row, 3, new QTableWidgetItem(status));
                break;
            }
        }
    }
}

void MainWindow::removeTransferFromTable(const QString &fileName)
{
    for (int row = 0; row < ui->tblTransfers->rowCount(); row++) {
        if (ui->tblTransfers->item(row, 0)->text() == fileName) {
            ui->tblTransfers->removeRow(row);
            progressBars.remove(fileName);
            break;
        }
    }
}

2.5 主程序入口 (main.cpp)

cpp 复制代码
#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    
    // 设置应用程序信息
    QApplication::setApplicationName("Qt File Transfer System");
    QApplication::setApplicationVersion("1.0");
    QApplication::setOrganizationName("Qt File Transfer");
    QApplication::setOrganizationDomain("qtfiletransfer.com");
    
    MainWindow w;
    w.show();
    
    return a.exec();
}

三、UI设计文件 (mainwindow.ui)

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>1200</width>
    <height>800</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Qt File Transfer System</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout" name="verticalLayout_2">
    <item>
     <widget class="QTabWidget" name="tabWidget">
      <property name="currentIndex">
       <number>0</number>
      </property>
      <widget class="QWidget" name="tabServer">
       <attribute name="title">
        <string>Server</string>
       </attribute>
       <layout class="QVBoxLayout" name="verticalLayout">
        <item>
         <widget class="QGroupBox" name="groupBox">
          <property name="title">
           <string>Server Settings</string>
          </property>
          <layout class="QGridLayout" name="gridLayout">
           <item row="0" column="0">
            <widget class="QLabel" name="label">
             <property name="text">
              <string>Port:</string>
             </property>
            </widget>
           </item>
           <item row="0" column="1">
            <widget class="QLineEdit" name="txtServerPort">
             <property name="maximumSize">
              <size>
               <width>100</width>
               <height>16777215</height>
              </size>
             </property>
            </widget>
           </item>
           <item row="0" column="2">
            <widget class="QPushButton" name="btnStartServer">
             <property name="text">
              <string>Start Server</string>
             </property>
            </widget>
           </item>
           <item row="0" column="3">
            <widget class="QPushButton" name="btnStopServer">
             <property name="text">
              <string>Stop Server</string>
             </property>
            </widget>
           </item>
           <item row="1" column="0">
            <widget class="QLabel" name="label_2">
             <property name="text">
              <string>Save Path:</string>
             </property>
            </widget>
           </item>
           <item row="1" column="1" colspan="2">
            <widget class="QLineEdit" name="txtServerSavePath"/>
           </item>
           <item row="1" column="3">
            <widget class="QPushButton" name="btnBrowseSavePath">
             <property name="text">
              <string>Browse...</string>
             </property>
            </widget>
           </item>
           <item row="2" column="0">
            <widget class="QLabel" name="lblServerStatus">
             <property name="text">
              <string>Stopped</string>
             </property>
             <property name="styleSheet">
              <string notr="true">QLabel { color: red; }</string>
             </property>
            </widget>
           </item>
          </layout>
         </widget>
        </item>
        <item>
         <widget class="QGroupBox" name="groupBox_2">
          <property name="title">
           <string>Server Log</string>
          </property>
          <layout class="QVBoxLayout" name="verticalLayout_3">
           <item>
            <widget class="QTextEdit" name="txtServerLog">
             <property name="readOnly">
              <bool>true</bool>
             </property>
            </widget>
           </item>
          </layout>
         </widget>
        </item>
       </layout>
      </widget>
      <widget class="QWidget" name="tabClient">
       <attribute name="title">
        <string>Client</string>
       </attribute>
       <layout class="QVBoxLayout" name="verticalLayout_4">
        <item>
         <widget class="QGroupBox" name="groupBox_3">
          <property name="title">
           <string>Connection Settings</string>
          </property>
          <layout class="QGridLayout" name="gridLayout_2">
           <item row="0" column="0">
            <widget class="QLabel" name="label_3">
             <property name="text">
              <string>Server Address:</string>
             </property>
            </widget>
           </item>
           <item row="0" column="1">
            <widget class="QLineEdit" name="txtServerAddress">
             <property name="maximumSize">
              <size>
               <width>150</width>
               <height>16777215</height>
              </size>
             </property>
            </widget>
           </item>
           <item row="0" column="2">
            <widget class="QLabel" name="label_4">
             <property name="text">
              <string>Port:</string>
             </property>
            </widget>
           </item>
           <item row="0" column="3">
            <widget class="QLineEdit" name="txtServerPortClient">
             <property name="maximumSize">
              <size>
               <width>100</width>
               <height>16777215</height>
              </size>
             </property>
            </widget>
           </item>
           <item row="0" column="4">
            <widget class="QPushButton" name="btnConnect">
             <property name="text">
              <string>Connect</string>
             </property>
            </widget>
           </item>
           <item row="0" column="5">
            <widget class="QPushButton" name="btnDisconnect">
             <property name="text">
              <string>Disconnect</string>
             </property>
            </widget>
           </item>
           <item row="1" column="0">
            <widget class="QLabel" name="lblClientStatus">
             <property name="text">
              <string>Disconnected</string>
             </property>
             <property name="styleSheet">
              <string notr="true">QLabel { color: red; }</string>
             </property>
            </widget>
           </item>
          </layout>
         </widget>
        </item>
        <item>
         <widget class="QGroupBox" name="groupBox_4">
          <property name="title">
           <string>File Transfer</string>
          </property>
          <layout class="QGridLayout" name="gridLayout_3">
           <item row="0" column="0">
            <widget class="QLabel" name="label_5">
             <property name="text">
              <string>File Path:</string>
             </property>
            </widget>
           </item>
           <item row="0" column="1" colspan="2">
            <widget class="QLineEdit" name="txtFilePath"/>
           </item>
           <item row="0" column="3">
            <widget class="QPushButton" name="btnBrowseFile">
             <property name="text">
              <string>Browse...</string>
             </property>
            </widget>
           </item>
           <item row="0" column="4">
            <widget class="QPushButton" name="btnSendFile">
             <property name="text">
              <string>Send File</string>
             </property>
            </widget>
           </item>
           <item row="0" column="5">
            <widget class="QPushButton" name="btnCancelTransfer">
             <property name="text">
              <string>Cancel</string>
             </property>
            </widget>
           </item>
           <item row="1" column="0" colspan="2">
            <widget class="QCheckBox" name="chkResume">
             <property name="text">
              <string>Resume Transfer (if supported)</string>
             </property>
             <property name="checked">
              <bool>true</bool>
             </property>
            </widget>
           </item>
          </layout>
         </widget>
        </item>
        <item>
         <widget class="QGroupBox" name="groupBox_5">
          <property name="title">
           <string>Client Log</string>
          </property>
          <layout class="QVBoxLayout" name="verticalLayout_5">
           <item>
            <widget class="QTextEdit" name="txtClientLog">
             <property name="readOnly">
              <bool>true</bool>
             </property>
            </widget>
           </item>
          </layout>
         </widget>
        </item>
       </layout>
      </widget>
      <widget class="QWidget" name="tabTransfers">
       <attribute name="title">
        <string>Active Transfers</string>
       </attribute>
       <layout class="QVBoxLayout" name="verticalLayout_6">
        <item>
         <widget class="QTableWidget" name="tblTransfers"/>
        </item>
       </layout>
      </widget>
     </widget>
    </item>
   </layout>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

四、项目配置文件 (CMakeLists.txt)

cmake 复制代码
cmake_minimum_required(VERSION 3.16)
project(QtFileTransfer VERSION 1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

# 查找Qt库
find_package(Qt6 COMPONENTS Core Widgets Network REQUIRED)

# 包含目录
include_directories(${CMAKE_CURRENT_SOURCE_DIR})

# 源文件
set(SOURCES
    main.cpp
    mainwindow.cpp
    fileserver.cpp
    fileclient.cpp
    transmissionprotocol.cpp
)

# 头文件
set(HEADERS
    mainwindow.h
    fileserver.h
    fileclient.h
    transmissionprotocol.h
)

# UI文件
set(UIS
    mainwindow.ui
)

# 创建可执行文件
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS} ${UIS})

# 链接库
target_link_libraries(${PROJECT_NAME} 
    Qt6::Core 
    Qt6::Widgets 
    Qt6::Network
)

# 安装规则
install(TARGETS ${PROJECT_NAME}
    RUNTIME DESTINATION bin
)

# 打包
set(CPACK_GENERATOR "ZIP")
set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
set(CPACK_PACKAGE_DESCRIPTION "Qt File Transfer System")
include(CPack)

参考代码 基于QT的文件传输 www.youwenfan.com/contentcsv/72043.html

五、使用说明

5.1 编译和运行

  1. 使用CMake构建项目:
bash 复制代码
mkdir build
cd build
cmake ..
make
  1. 运行程序:
bash 复制代码
./QtFileTransfer

5.2 使用步骤

  1. 启动服务器

    • 切换到"Server"标签页
    • 设置端口(默认8888)
    • 设置文件保存路径
    • 点击"Start Server"
  2. 连接客户端

    • 切换到"Client"标签页
    • 输入服务器地址(如127.0.0.1)
    • 输入服务器端口(默认8888)
    • 点击"Connect"
  3. 发送文件

    • 点击"Browse..."选择要发送的文件
    • 勾选"Resume Transfer"启用断点续传
    • 点击"Send File"开始传输
  4. 监控传输

    • 切换到"Active Transfers"标签页查看所有传输进度
    • 在服务器和客户端的日志区域查看详细日志

5.3 功能特点

  • 断点续传:支持在传输中断后从中断处继续传输
  • 多文件传输:可以同时监控多个传输任务
  • 进度显示:实时显示传输进度和速度
  • 日志记录:详细记录传输过程和错误信息
  • 跨平台:基于Qt,可在Windows、Linux、macOS上运行
相关推荐
yuan199971 小时前
基于 MATLAB PSO 工具箱的函数寻优算法
开发语言·算法·matlab
誰能久伴不乏2 小时前
ibmodbus “Invalid argument“ 错误的排查与修复
c++·qt·modbus
handler012 小时前
【C++】二叉搜索树详解及其模拟实现(代码)
开发语言·c++·算法·c··二叉搜索树·搜索树
luj_17682 小时前
残熵算法的稳健防灾逻辑
c语言·开发语言·c++·经验分享·算法
一只鹿鹿鹿2 小时前
信息化项目管理规范(参考Word文件)
java·大数据·运维·开发语言·数据库
XGeFei2 小时前
python中子线程与主线程的关系
开发语言·python
Chase_______3 小时前
【Java杂项】final 关键字详解:变量、方法、类限制与引用可变性
java·开发语言·python
ruxingli3 小时前
Golang iota详解
开发语言·后端·golang
我材不敲代码3 小时前
Python venv 虚拟环境从入门到精通 + uv 高性能替代工具实战指南
开发语言·python·uv