Qt TCP網絡編程——傳輸圖片(附TCP連接邏輯以及完整代碼)

0 效果

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
完整代碼地址

1 知識點

1.1 圖片編碼和解碼

png編碼爲base64數據 :(用於客服端傳輸)

QByteArray Client::getImageData(const QImage &image)
{
    QByteArray imageData;
    QBuffer buffer(&imageData);
    image.save(&buffer, "png");
    imageData = imageData.toBase64();
    
    return imageData;
}

base64數據解碼爲png :(用於客服端傳輸)

QImage Server::getImage(const QString &data)
{
    QByteArray imageData = QByteArray::fromBase64(data.toLatin1());
    QImage image;
    image.loadFromData(imageData);
    return image;
}

1.2 圖片顯示(合理縮放圖像以填充label)

           QImage imageData = getImage(imageContent);
            QPixmap resImage = QPixmap::fromImage(imageData);
            QPixmap* imgPointer = &resImage;
            imgPointer->scaled(ui->imageLabel->size(), Qt::IgnoreAspectRatio);//重新調整圖像大小以適應窗口
            //法二:替換前面的表達式
           // imgPointer->scaled(ui->imageLabel->size(), Qt::KeepAspectRatio);//設置pixmap縮放的尺寸

            ui->imageLabel->setScaledContents(true);//設置label的屬性,能夠縮放pixmap充滿整個可用的空間。
            ui->imageLabel->setPixmap(*imgPointer);

在這裏插入圖片描述

1.3 TCP傳輸與接收

  • 信號:
    • 客戶端連接上服務器時,發送SIGNAL(connected())信號;
    • 客戶端的連接被斷開時,發送SIGNAL(disconnected())信號;
    • 客戶端中的tcpClient->write(outBlock);,發送readyRead()信號【readyRead()當網絡套接字上有新的網絡數據有效負載時】;
    • 兩個端出現錯誤時,發出SIGNAL(error(QAbstractSocket::SocketError))信號;
    • 每當有客戶端連接服務器後,會發送SIGNAL(newConnection())信號;
  • 連接邏輯
    • 1 首先服務器通過(QTcpServer 對象).listen(ip地址,端口)開始監聽端口;
    • 2 客戶端通過(QTcpSocket* 對象)->connectToHost(ip地址,端口)連接服務器,當連接上服務器時,對於客戶端會發出SIGNAL(connected())信號,對於服務器端會發送SIGNAL(newConnection())信號;
    • 3 服務器端收到SIGNAL(newConnection())信號後,使用(QTcpServer對象).nextPendingConnection() 獲得連接套接字;
    • 4 客戶端通過(QTcpSocket *對象)->write(QByteArray對象)發出readyRead()信號(把數據寫入TCP包中);
    • 5 服務器端收到readyRead()信號後,開始處理信息,顯示圖片。
    • 6 服務器端斷開連接時(QTcpSocket*對象)->disconnectFromHost();,會發出SIGNAL(disconnected())信號,然後客戶端更新狀態

1.4 TCP知識

如果只是發送空的圖片,就是隻有TCP報頭的數據報,大小爲20字節。

一般以太網幀的大小爲[64,1518],除去幀頭14字節(6字節目的MAC地址,6字節目的MAC地址,2字節Tye域值)、4字節幀尾(CRC校驗),剩下大小爲[64,1500]字節,不足64字節會被當作無效幀(因爲爭用期的存在,即確保在發送數據的同時檢測到可能存在的衝突【CSMA/CD協議】)。

一個TCP報文大小最大爲1500-20(IP頭)-20(TCP頭)=1460字節;
一個UPD報文最大爲1500-20(IP頭)-8(UPD頭)=1482字節。

TCP全靠IP曾來分幀(流協議),TCP協議本身會進行擁賽和流利控制。

2 客戶端

類的聲明

private:
    Ui::Client *ui;

    QTcpSocket *tcpClient;
    QFile *localFile;     // 要發送的文件
    qint64 totalBytes;    // 發送數據的總大小
//    qint64 bytesWritten;  // 已經發送數據大小
//    qint64 bytesToWrite;  // 剩餘數據大小
    qint64 payloadSize;   // 每次發送數據的大小(64k) 未用到
    QString fileName;     // 保存文件路徑
    QByteArray outBlock;  // 數據緩衝區,即存放每次要發送的數據塊

    QImage image;//圖片
    QString currentImageName;//圖片名

    volatile bool isOk;

private slots:
    void openFile();//打開文件
    void send();//發送
    void connectServer();//連接服務器
    void startTransfer();//發送圖片數據
    void displayError(QAbstractSocket::SocketError);//處理錯誤函數
    void tcpConnected();//更新isOk的值,按鈕以及label的顯示
    void tcpDisconnected();//斷開連接處理的事件

    //圖片轉base64字符串
    QByteArray getImageData(const QImage&);

    void on_openButton_clicked();//打開圖片
    void on_sendButton_clicked();//發送圖片
    void on_connectButton_clicked();//連接或斷開服務器
signals:
    void buildConnected();//連接上服務器後,發出的信號

類的構造函數:

Client::Client(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Client)
{
    ui->setupUi(this);

    //地址和端口自動補全以及默認提示
    QStringList hostWordList, portWordList;
    hostWordList <<tr("127.0.0.1");
    portWordList << tr("6666");
    QCompleter* completerHost = new QCompleter(hostWordList, this);
    QCompleter* completerPort = new QCompleter(portWordList, this);

    ui->hostLineEdit->setCompleter(completerHost);
    ui->portLineEdit->setCompleter(completerPort);
    ui->hostLineEdit->setPlaceholderText(tr("127.0.0.1"));
    ui->portLineEdit->setPlaceholderText(tr("6666"));

    payloadSize = 64 * 1024; // 64KB
    totalBytes = 0;
//    bytesWritten = 0;
//    bytesToWrite = 0;
    isOk = false;
    
    ui->sendButton->setEnabled(false);

    tcpClient = new QTcpSocket(this);

    // 當連接服務器成功時,發出connected()信號,isOK置爲true
    connect(tcpClient, SIGNAL(connected()), this, SLOT(tcpConnected()));
    //當點擊發送按鈕後(且isOK爲true),發出buildConnected()信號,開始傳送數據
    connect(this, SIGNAL(buildConnected()), this, SLOT(startTransfer()));
    //當斷開連接時,發出disconnected(),isOK置爲false
    connect(tcpClient, SIGNAL(disconnected()), this, SLOT(tcpDisconnected()));
    //顯示錯誤
    connect(tcpClient, SIGNAL(error(QAbstractSocket::SocketError)),
            this, SLOT(displayError(QAbstractSocket::SocketError)));
}

其餘成員函數以及槽函數的定義:

void Client::openFile()
{
    fileName = QFileDialog::getOpenFileName(this);
    
    if (!fileName.isEmpty()) {
        
        //獲得實際文件名
        currentImageName = fileName.right(fileName.size()
                                                 - fileName.lastIndexOf('/')-1);

        ui->clientStatusLabel->setText(tr("打開 %1 文件成功!").arg(currentImageName));

        if(isOk == true){
            ui->sendButton->setEnabled(true);
        }
    }
}

void Client::send()
{
    if(!isOk){
        ui->clientStatusLabel->setText(tr("請先連接服務器"));
        return;
    }else{
        //發射信號
        emit buildConnected();
        qDebug() << "emit buildConnected()" << endl;
    }
}

void Client::connectServer()
{
    // 初始化已發送字節爲0
//    bytesWritten = 0;
    ui->clientStatusLabel->setText(tr("連接中…"));

//    //連接到服務器
    tcpClient->connectToHost(ui->hostLineEdit->text(),
                             ui->portLineEdit->text().toInt());

    isOk = true;
    qDebug() << "connectServer: isOk is ok" << endl;
}


void Client::startTransfer()
{
    QDataStream sendOut(&outBlock, QIODevice::WriteOnly);
    sendOut.setVersion(QDataStream::Qt_5_6);

   //獲得圖片數據
    QImage image(fileName);
    QString imageData = getImageData(image);

    qDebug() << "fileName: " <<fileName << endl;
//    qDebug() << "imageData" << imageData << endl;
    
    // 保留總大小信息空間、圖像大小信息空間,然後輸入圖像信息
    sendOut << qint64(0) << qint64(0) << imageData;

    // 這裏的總大小是總大小信息、圖像大小信息和實際圖像信息的總和
    totalBytes += outBlock.size();
    sendOut.device()->seek(0);

    // 返回outBolock的開始,用實際的大小信息代替兩個qint64(0)空間
    sendOut << totalBytes << qint64((outBlock.size() - sizeof(qint64)*2));

    //發出readyRead()信號
    tcpClient->write(outBlock);

    qDebug() << "圖片的內容大小: " << qint64((outBlock.size() - sizeof(qint64)*2)) << endl;
    qDebug() << "整個包的大小: " << totalBytes << endl;
//    qDebug() << "發送完文件頭結構後剩餘數據的大小(bytesToWrite): " << bytesToWrite <<endl;

    outBlock.resize(0);

    ui->clientStatusLabel->setText(tr("傳送文件 %1 成功").arg(currentImageName));
    totalBytes = 0;
//    bytesToWrite = 0;
}

void Client::displayError(QAbstractSocket::SocketError)
{
    qDebug() << tcpClient->errorString();
    tcpClient->close();

    ui->clientStatusLabel->setText(tr("客戶端就緒"));
    ui->sendButton->setEnabled(true);
}

void Client::tcpConnected()
{
    isOk = true;
    ui->connectButton->setText(tr("斷開"));

    ui->clientStatusLabel->setText(tr("已連接"));
}

void Client::tcpDisconnected()
{
    isOk = false;
    tcpClient->abort();
    ui->connectButton->setText(tr("連接"));

    ui->clientStatusLabel->setText(tr("連接已斷開"));
}

QByteArray Client::getImageData(const QImage &image)
{
    QByteArray imageData;
    QBuffer buffer(&imageData);
    image.save(&buffer, "png");
    imageData = imageData.toBase64();
    
    return imageData;
}

// 打開按鈕
void Client::on_openButton_clicked()
{
    ui->clientStatusLabel->setText(tr("狀態:等待打開文件!"));
    openFile();

}

// 發送按鈕
void Client::on_sendButton_clicked()
{
    send();
}

void Client::on_connectButton_clicked()
{
    if (ui->connectButton->text() == tr("連接")) {
        tcpClient->abort();
        connectServer();
    } else {
        tcpClient->abort();
    }
}

佈局:
在這裏插入圖片描述

3 服務器

類聲明:

private:
    Ui::Server *ui;

    QTcpServer tcpServer;
    QTcpSocket *tcpServerConnection;
    qint64 totalBytes;     // 存放總大小信息
    qint64 bytesReceived;  // 已收到數據的大小
    qint64 fileNameSize;   // 文件名的大小信息
    qint64 imageSize; //圖片大小

    QString fileName;      // 存放文件名
    QFile *localFile;      // 本地文件
    QByteArray inBlock;    // 數據緩衝區
    QString imageContent;

    QImage image;//圖片


private slots:
    void start();//監聽的事件
    void acceptConnection();//被客戶端連接上後,創建套接字、接收數據、處理異常、關閉服務器
    void updateServerProgress();//接收並處理顯示圖片
    void displayError(QAbstractSocket::SocketError socketError);//錯誤處理

    //base64字符串轉圖片
    QImage getImage(const QString &);

    void on_startButton_clicked();//監聽或斷開監聽

構造函數:

    ui->setupUi(this);

    //端口自動補全以及默認提示
    ui->portLineEdit->setPlaceholderText(tr("6666"));//設置默認提示
    QStringList portWordList;
    portWordList << tr("6666");
    QCompleter* portCompleter = new QCompleter(portWordList, this);
    ui->portLineEdit->setCompleter(portCompleter);

    connect(&tcpServer, SIGNAL(newConnection()),
            this, SLOT(acceptConnection()));


    ui->imageLabel->show();

其餘函數的定義:

void Server::start()
{


    if (!tcpServer.listen(QHostAddress::LocalHost, ui->portLineEdit->text().toInt())) {
        qDebug() << tcpServer.errorString();
        close();
        return;
    }

    totalBytes = 0;
    bytesReceived = 0;
    imageSize = 0;
    ui->serverStatusLabel->setText(tr("正在監聽"));

}

void Server::acceptConnection()
{
    //獲得鏈接套接字
    tcpServerConnection = tcpServer.nextPendingConnection();

    //接收數據
    //readyRead()當網絡套接字上有新的網絡數據有效負載時
    connect(tcpServerConnection, SIGNAL(readyRead()),
            this, SLOT(updateServerProgress()));
    //處理異常
    connect(tcpServerConnection, SIGNAL(error(QAbstractSocket::SocketError)),
            this, SLOT(displayError(QAbstractSocket::SocketError)));

    ui->serverStatusLabel->setText(tr("接受連接"));
    // 關閉服務器,不再進行監聽
//    tcpServer.close();
}


void Server::updateServerProgress()
{
    QDataStream in(tcpServerConnection);
    in.setVersion(QDataStream::Qt_5_6);

    // 如果接收到的數據小於16個字節,保存到來的文件頭結構
    if (bytesReceived <= sizeof(qint64)*2) {
        if((tcpServerConnection->bytesAvailable() >= sizeof(qint64)*2)
                && (imageSize == 0)) {
            // 接收數據總大小信息和文件名大小信息
            in >> totalBytes  >> imageSize;
            bytesReceived += sizeof(qint64) * 2;

            if(imageSize == 0){
                  ui->serverStatusLabel->setText(tr("顯示的圖片爲空!"));
            }
              qDebug() <<"定位點0" << endl;
        }
        if((tcpServerConnection->bytesAvailable() >= imageSize)
                && (imageSize != 0)) {

            // 接收文件名,並建立文件
            in >> imageContent;

//            qDebug() << imageContent << endl;

            ui->serverStatusLabel->setText(tr("接收文件 …"));

            QImage imageData = getImage(imageContent);

            QPixmap resImage = QPixmap::fromImage(imageData);
            QPixmap* imgPointer = &resImage;
            imgPointer->scaled(ui->imageLabel->size(), Qt::IgnoreAspectRatio);//重新調整圖像大小以適應窗口
           // imgPointer->scaled(ui->imageLabel->size(), Qt::KeepAspectRatio);//設置pixmap縮放的尺寸

            ui->imageLabel->setScaledContents(true);//設置label的屬性,能夠縮放pixmap充滿整個可用的空間。
            ui->imageLabel->setPixmap(*imgPointer);

            bytesReceived += imageSize;

            qDebug() << "定位1  bytesReceived: " << bytesReceived << endl;

            if(bytesReceived == totalBytes){
                 ui->serverStatusLabel->setText(tr("接收文件成功"));
                 totalBytes = 0;
                 bytesReceived = 0;
                 imageSize = 0;
            }

         }
     }
}

void Server::displayError(QAbstractSocket::SocketError socketError)
{
    qDebug() <<"errorString()" <<tcpServerConnection->errorString();
    tcpServerConnection->close();

    ui->serverStatusLabel->setText(tr("服務端就緒"));

}

QImage Server::getImage(const QString &data)
{
    QByteArray imageData = QByteArray::fromBase64(data.toLatin1());
    QImage image;
    image.loadFromData(imageData);
    return image;
}

// 開始監聽按鈕

void Server::on_startButton_clicked()
{
    if(ui->startButton->text() == tr("監聽")){
        ui->startButton->setText(tr("斷開"));
        start();
    }else{
        ui->startButton->setText(tr("監聽"));
        tcpServer.close();

        tcpServerConnection->disconnectFromHost();
    }
}

4 代碼下載

https://download.csdn.net/download/qq_33375598/12340133

5 不足

  • 沒有心跳機制來判斷離線,進而自動重連服務器,沒有設置超時時間;
  • 沒有使用線程進行圖片傳輸
  • 沒有實現多個客戶端和一個服務端通信
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章