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 不足

  • 没有心跳机制来判断离线,进而自动重连服务器,没有设置超时时间;
  • 没有使用线程进行图片传输
  • 没有实现多个客户端和一个服务端通信
相关推荐
©️2020 CSDN 皮肤主题: 岁月 设计师:pinMode 返回首页