QT使用tcp協議上傳文件服務端與客戶端源代碼類

參考了網上的源代碼,整理修改成了單獨的類,使用起來非常方便,每個類的使用方法在實現文件的上方用註釋的方便說明了,歡迎交流與反饋。

直接上源碼,先上服務端:

#ifndef TCPUPLOADSERVER_H
#define TCPUPLOADSERVER_H

#include <QObject>
#include <QAbstractSocket>
#include <QTcpServer>
#include <QLabel>

class QTcpSocket;
class QFile;

class TcpUploadServer : public QObject
{
    Q_OBJECT
public:
    explicit TcpUploadServer(QObject *parent = nullptr);

    bool StartServer();
    void SetLableStatus(QLabel *lableStatus){m_lableStatus = lableStatus;}
    void SetPort(const quint16& nPort){m_nPort = nPort;}

signals:
    void begin();
    void progress(qint64, qint64);
    void finished();

public slots:
    void acceptConnection();
    void updateServerProgress();
    void displayError(QAbstractSocket::SocketError socketError);

private:
    //界面相關
    QLabel *m_lableStatus;
    quint16 m_nPort;

    QTcpServer m_tcpServer;
    QTcpSocket *m_tcpServerConnection;
    qint64 m_totalBytes;
    qint64 m_bytesReceived;
    qint64 m_fileNameSize;
    QString m_fileName;
    QFile *m_localFile;
    QByteArray m_inBlock;

};

#endif // TCPUPLOADSERVER_H
#include "TcpUploadServer.h"
#include <QtNetwork>

/*
    TcpUploadServer *server = new TcpUploadServer(this);
    connect(server, SIGNAL(begin()), this, SLOT(beginReceive()));
    connect(server, SIGNAL(progress(qint64, qint64)), this, SLOT(receiveProgress(qint64, qint64)));
    connect(server, SIGNAL(finished()), this, SLOT(receiveFinished()));
    server->SetLableStatus(ui->lb_StatusServer);
    server->SetPort(9999);
    server->StartServer();
*/

TcpUploadServer::TcpUploadServer(QObject *parent) : QObject(parent)
{
    m_nPort = 0;
    m_lableStatus = nullptr;
    m_tcpServerConnection = nullptr;
    m_localFile = nullptr;
    connect(&m_tcpServer, SIGNAL(newConnection()), this, SLOT(acceptConnection()));
}

bool TcpUploadServer::StartServer()
{
    emit progress(0, 100);
    if(!m_tcpServer.listen(QHostAddress::LocalHost, m_nPort))
    {
        qDebug()<<m_tcpServer.errorString();
        return false;
    }
    qDebug() << "服務啓動!";
    qDebug() << "監聽端口" << m_nPort << "成功!";

    m_totalBytes=0;
    m_bytesReceived=0;
    m_fileNameSize=0;

    if (m_lableStatus)
    {
        m_lableStatus->setText("監聽");
    }

    return true;
}

void TcpUploadServer::acceptConnection()
{
    emit begin();
    m_tcpServerConnection=m_tcpServer.nextPendingConnection();
    connect(m_tcpServerConnection, SIGNAL(readyRead()), this, SLOT(updateServerProgress()));
    connect(m_tcpServerConnection, SIGNAL(error(QAbstractSocket::SocketError)), this ,SLOT(displayError(QAbstractSocket::SocketError)));

    if (m_lableStatus)
    {
        m_lableStatus->setText("接受連接");
    }

    //關閉服務器不再監聽,直接進入數據收發
    m_tcpServer.close();
}

void TcpUploadServer::updateServerProgress()
{
    QDataStream in(m_tcpServerConnection);
    in.setVersion(QDataStream::Qt_5_7);
    // 如果接收到的數據小於16個字節,保存到來的文件頭結構
    if (m_bytesReceived<=sizeof(qint64)*2)
    {
        if((m_tcpServerConnection->bytesAvailable()>=sizeof(qint64)*2)&&(m_fileNameSize==0))
        {
            // 接收數據總大小信息和帶路徑的文件名大小信息
            in>>m_totalBytes>>m_fileNameSize;
            m_bytesReceived +=sizeof(qint64)*2;
        }
        if((m_tcpServerConnection->bytesAvailable()>=m_fileNameSize)&&(m_fileNameSize!=0))
        {
            // 接收文件名,並建立文件
            in>>m_fileName;

            if (m_lableStatus)
            {
                m_lableStatus->setText(tr("接收文件 %1 …").arg(m_fileName));
            }

            m_bytesReceived+=m_fileNameSize;
            m_localFile = new QFile(m_fileName);
            if (!m_localFile->open(QFile::WriteOnly))
            {
                qDebug() << "server: open file error!";
                return;
            }
        }
        else
        {
            return;
        }
    }
    // 如果接收的數據小於總數據,那麼寫入文件
    if(m_bytesReceived<m_totalBytes)
    {
        m_bytesReceived+=m_tcpServerConnection->bytesAvailable();
        m_inBlock = m_tcpServerConnection->readAll();
        m_localFile->write(m_inBlock);
        m_inBlock.resize(0);
    }

    emit progress(m_bytesReceived, m_totalBytes);

    // 接收數據完成時
    if (m_bytesReceived==m_totalBytes){
        m_tcpServerConnection->close();
        m_localFile->close();

        if (m_lableStatus)
        {
            m_lableStatus->setText(tr("接收文件 %1 成功!").arg(m_fileName));
        }

        emit finished();
        //繼續監聽
        StartServer();
    }
}

void TcpUploadServer::displayError(QAbstractSocket::SocketError socketError)
{
    Q_UNUSED(socketError)
    qDebug()<<m_tcpServerConnection->errorString();
    m_tcpServerConnection->close();
    if (m_lableStatus)
    {
        m_lableStatus->setText("出現錯誤,可能因爲同時上傳多個文件!");
    }
    StartServer();
}

接着是客戶端:

#ifndef TCPUPLOADCLIENT_H
#define TCPUPLOADCLIENT_H

#include <QObject>
#include <QLabel>
#include <QLineEdit>
#include <QAbstractSocket>

class QTcpSocket;
class QFile;

class TcpUploadClient : public QObject
{
    Q_OBJECT
public:
    explicit TcpUploadClient(QObject *parent = nullptr);

    void SetLableStatus(QLabel *lableStatus){m_lableStatus = lableStatus;}
    void SetHostAndPort(const QString& strHost, const quint16& nPort){m_strHost = strHost; m_nPort = nPort;}
    //需要用QLineEdit顯示主機和端口調用
    void SetLineEditHostAndPort(QLineEdit *leHost, QLineEdit *lePort){m_leHost = leHost;m_lePort = lePort;}
    void SetUploadFilePath(const QString& strFilePath);

    //strServerFileName不能爲空 用於指定要存儲到服務器上的文件名,可以與本地文件名不同
    void StartUpload(const QString& strLoaclPath, const QString& strServerFileName);

    //獲取文件名,包含後綴
    static QString GetNameOfFile(const QString& strFullPath){return strFullPath.right(strFullPath.size()-strFullPath.lastIndexOf('/')-1);}

signals:
    void progress(qint64, qint64);
    void finished();

public slots:
    void send();
    void startTransfer();
    void updateClientProgress(qint64);
    void displayError(QAbstractSocket::SocketError);

private:
    //界面相關
    QLineEdit *m_leHost;
    QLineEdit *m_lePort;
    QLabel *m_lableStatus;

    QString m_strHost;
    quint16 m_nPort;

    QTcpSocket *m_tcpClient;
    QFile *m_localFile;
    qint64 m_totalBytes;
    qint64 m_bytesWritten;
    qint64 m_bytesToWrite;
    qint64 m_payloadSize;
    QString m_fileName;
    QString m_strServerFileName;
    QByteArray m_outBlock;
};

#endif // TCPUPLOADCLIENT_H
#include "TcpUploadClient.h"
#include <QtNetwork>

/*
        TcpUploadClient *tuc = new TcpUploadClient(this);
        connect(tuc, SIGNAL(progress(qint64, qint64)), this, SLOT(uploadProgress(qint64, qint64)));
        connect(tuc, SIGNAL(finished()), this, SLOT(uploadFinished()));
        tuc->SetHostAndPort("localhost", 9999);
        tuc->SetLineEditHostAndPort(ui->lineEditUrl,  ui->lineEditPort);
        tuc->SetLableStatus(ui->lb_StatusClient);
        tuc->StartUpload(strFilePath, tuc->GetNameOfFile(strFilePath));
*/

TcpUploadClient::TcpUploadClient(QObject *parent) : QObject(parent)
{
    m_payloadSize=64*1024;
    m_totalBytes=0;
    m_bytesWritten=0;
    m_bytesToWrite=0;
    m_tcpClient=new QTcpSocket(this);
    connect(m_tcpClient, SIGNAL(connected()), this,SLOT(startTransfer()));
    connect(m_tcpClient, SIGNAL(bytesWritten(qint64)), this, SLOT(updateClientProgress(qint64)));
    connect(m_tcpClient, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(displayError(QAbstractSocket::SocketError)));

    m_nPort = 0;
    m_leHost = nullptr;
    m_lePort = nullptr;
    m_lableStatus = nullptr;
}

void TcpUploadClient::SetUploadFilePath(const QString &strFilePath)
{
    if (strFilePath.length())
    {
        m_fileName = strFilePath;
        if (m_lableStatus)
        {
            m_lableStatus->setText(QString("打開文件 %1 成功!").arg(m_fileName));
        }
    }
}

void TcpUploadClient::StartUpload(const QString &strLoaclPath, const QString &strServerFileName)
{
    //存儲在服務器上的地址
    m_strServerFileName = strServerFileName;
    SetUploadFilePath(strLoaclPath);
    send();
}

void TcpUploadClient::send()
{
    m_bytesWritten=0;

    if (m_lableStatus)
    {
        m_lableStatus->setText("連接中...");
    }
    emit progress(0, 100);
    m_tcpClient->connectToHost(m_strHost,  m_nPort);
}

void TcpUploadClient::startTransfer()
{
    m_localFile=new QFile(m_fileName);
    if(!m_localFile->open(QFile::ReadOnly)){
        qDebug()<<"client:open file error!";
        return;
    }
    m_totalBytes=m_localFile->size();
    QDataStream sendOut(&m_outBlock,QIODevice::WriteOnly);
    sendOut.setVersion(QDataStream::Qt_5_7);
    //文件總大小、文件名大小、文件名
    sendOut<<qint64(0)<<qint64(0)<<m_strServerFileName;
    m_totalBytes+=m_outBlock.size();
    sendOut.device()->seek(0);
    sendOut<<m_totalBytes<<qint64(m_outBlock.size()-sizeof(qint64)*2);
    m_bytesToWrite=m_totalBytes-m_tcpClient->write(m_outBlock);

    if (m_lableStatus)
    {
        m_lableStatus->setText("已連接");
    }

    m_outBlock.resize(0);
}

void TcpUploadClient::updateClientProgress(qint64 numBytes)
{
    m_bytesWritten+=(int)numBytes;
    if(m_bytesToWrite>0)
    {
        m_outBlock=m_localFile->read(qMin(m_bytesToWrite,m_payloadSize));
        m_bytesToWrite-=(int)m_tcpClient->write(m_outBlock);
        m_outBlock.resize(0);
    }
    else{
        m_localFile->close();
    }

    //發送進度
    emit progress(m_bytesWritten, m_totalBytes);

    if(m_bytesWritten==m_totalBytes)
    {
        if (m_lableStatus)
        {
            m_lableStatus->setText(QString("傳送文件 %1 成功").arg(m_fileName));
        }
        m_localFile->close();
        m_tcpClient->close();
        emit finished();
    }
}

void TcpUploadClient::displayError(QAbstractSocket::SocketError)
{
    qDebug()<<m_tcpClient->errorString();
    m_tcpClient->close();
    emit progress(0, 100);

    if (m_lableStatus)
    {
        m_lableStatus->setText("上次傳輸出現錯誤!客戶端重新就緒!");
    }
}

感謝這位作者IT1995精彩的代碼,最初的代碼地址:Qt學習筆記-Qt實現文件傳輸功能(基於TCP)【客戶端傳給服務器】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章