专栏首页Pou光明Qt Socket 收发图片——图像拆包、组包、粘包处理(二)

Qt Socket 收发图片——图像拆包、组包、粘包处理(二)

之前给大家分享了一个使用python发图片数据、Qt server接收图片的Demo。之前的Demo用于传输小字节的图片是可以的,但如果是传输大的图片,使用socket无法一次完成发送该怎么办呢?本次和大家分享一个对大的图片拆包、组包、处理粘包的例子。

程序平台:ubuntu 、 Qt 5.5.1

为了对接收到的图像字节进行组包,我们需要对每包数据规定协议,协议如下图:

每包数据前10个字节对应含义如下:前两个字节对应数据包类型,中间四字节预留,最后四字节是包内数据实际长度。对应协议图片更方便刚开始上手的兄弟理解。

对协议有了一个了解后,接下来说下程序结构。客户端按照协议发送图片字节,服务器接收字节,如果客户端发多少服务器就收多少那可真是太好了,然而意外总是如期而至。服务器这边由于socket的缓冲总是会粘包,所以服务器这边主要工作是拆包和组包,这也是整个程序组中最重要的部分。其次就是服务器在接收图片时为了响应更及时,单独使用一个线程进行接收图片,这里面我使用的是Qt的moveToThread。也使用过linux的socket以及线程接收图片,感觉性能要比Qt封装过的要好,大家有需要的话可以在公众号后台留言。

接下来跟着程序走:

1. 客户端发送部分:

void Widget::on_pbn_readPicture_clicked()
{
    m_picturePath = m_picturePath +"/auboi5.jpg";
    QPixmap pix;
    bool ret = pix.load(m_picturePath);

    QBuffer buffer;
    buffer.open(QIODevice::ReadWrite);
    bool ret2 = pix.save(&buffer,"jpg");

    m_pictureByteArray = buffer.data();

    if(ret2)
    {
        QString str = "read image finish!";
        ui->textEdit->append(str);
    }
}

读取图片字节主要用到了Qt的QPixmap 类,这个不细说,大家具体可参考Qt文档。图片字节被读取到m_picture ByteArray中,成功后在textEdit显示read image finish!。

②发送图像拆包

QByteArray dataPackage;

    // command 0 ,package total size
    QDataStream dataHead(&dataPackage,QIODevice::ReadWrite);
    dataHead << quint16(0);
    dataHead << quint32(0);
    dataHead << quint32(m_pictureByteArray.size());
    dataPackage.resize(40960);
    mp_clsTcpSocket->write(dataPackage);
    dataPackage.clear();

    QThread::msleep(20);

这里我拿一包数据举例说明。第一包数据是将读取到的整张图片的大小发送出去,以判断接收方接收到的数据是否完整。主要涉及到Qt一些数据类型的转换,如将整型字节存入QByteArray 中使用QDataStream 。之后将数据包大小重新设置为40960,方便服务器处理粘包。

③发送utf8 编码的中文

void Widget::on_pbn_sendChinese_clicked()
{
    QByteArray dataPackage;
    QByteArray chinese = "阶级终极形态假设!";

    //command 3
    QDataStream dataTail(&dataPackage,QIODevice::ReadWrite);
    dataTail << quint16(3);
    dataTail << quint32(0);
    dataTail << quint32(chinese.size());

    dataPackage = dataPackage.insert(10,chinese.data(),chinese.size());
    dataPackage.resize(40960);

    mp_clsTcpSocket->write(dataPackage);
}

这部分直接略过了,大家参考下即可。

2. 服务器接收部分(重要):

①线程中槽函数接收图片数据拆包

void TcpServerRecvImage::slot_readClientData()
{
    QByteArray buffer;
    buffer = mp_clsTcpClientConnnect->readAll();

    m_bufferSize = buffer.size();
    m_total = m_total + buffer.size();
    qDebug() << "socket Receive Data size:" << m_bufferSize << m_total;

    if(m_bufferSize == 40960)
    {
        emit signal_sendImagedataPackage(buffer);
        qDebug() << "直接发送";
        return;
    }


    if((m_picture.size() + m_bufferSize) == 40960)
    {
        m_picture.append(buffer);

        emit signal_sendImagedataPackage(m_picture);
        m_picture.clear();
        qDebug() << "拼接后40960";
        return;
    }


    if((m_picture.size() + m_bufferSize) < 40960)
    {
        m_picture.append(buffer) ;
        qDebug() << "直接拼接";
        return;
    }

    if((m_picture.size() + m_bufferSize) > 40960)
    {
        //case one
        if((m_bufferSize > 40960) && (m_picture.size() == 0))
        {
            while(m_bufferSize/40960)
            {
                QByteArray data = buffer.left(40960);
                buffer.remove(0,40960);

                emit signal_sendImagedataPackage(data);
                m_bufferSize = buffer.size();

                if((m_bufferSize/40960 == 0) && (m_bufferSize!=0))
                {
                    m_picture.append(buffer);
                }
                QThread::msleep(2);
            }
            return;
        }

        //case two
        if((m_bufferSize > 40960) && (m_picture.size() > 0))
        {
            int frontLength = 40960 - m_picture.size();
            QByteArray data = buffer.left(frontLength);
            buffer.remove(0,frontLength);

            m_picture.append(data);
            if(40960 == m_picture.size())
            {
                emit signal_sendImagedataPackage(m_picture);
                m_picture.clear();
            }

            m_bufferSize = buffer.size();

            while(m_bufferSize/40960)
            {
                QByteArray data = buffer.left(40960);
                buffer.remove(0,40960);

                emit signal_sendImagedataPackage(data);
                m_bufferSize = buffer.size();

                if((m_bufferSize/40960 == 0) && (m_bufferSize!=0))
                {
                    m_picture.append(buffer);
                }
                QThread::msleep(2);
            }
            return;
        }
    }
}

程序有那么一点长,我先说下他们在做的事情:

1> 如果接收到的字节是40960字节,直接发到主线程处理数据的槽中

2> 如果接收到的字节加上缓存中的字节数目小于40960,直接将数据追加到 m_picture中 【请原谅我40960没有用宏定义】

3> 如果接收到的字节加上缓存中的字节数目等于40960,直接发送

4> 如果接收到的字节加上缓存中的字节数目大于40960,分两种

①接收到的字节是40960的整数倍

                if((m_bufferSize/40960 == 0) && (m_bufferSize!=0))
                {
                    m_picture.append(buffer);
                }

如果不加上面这个追加函数,则会有数据解析失败

②接收到的字节不是40960的整数倍

              int frontLength = 40960 - m_picture.size();
              QByteArray data = buffer.left(frontLength);
              buffer.remove(0,frontLength);

先取出那一包数据剩余的部分,然后拼成一包发 出。

之前试过直接追加到m_picture中,但经常有数据解析失败,

然后看例子,试了这个,结果......

②主线程处理40960数据包

void Widget::slot_imagePackage(QByteArray imageArray)
{
    m_imageCount++;
    QString number = QString::number(m_imageCount);
    ui->textEdit->append(number);

    QByteArray cmdId = imageArray.left(2);
    QDataStream commandId(cmdId);
    quint16 size;
    commandId >> size;

    if(0 == size)
    {
        QByteArray cmdId = imageArray.mid(6,9);
        QDataStream commandId(cmdId);
        quint32 size;
        commandId >> size;
        qDebug() << "图片的总字节数" << size;
    }

    if(2 == size)
    {
        QByteArray cmdId = imageArray.mid(6,9);
        QDataStream commandId(cmdId);
        quint32 size;
        commandId >> size;
        qDebug() << "图片包尾字节数 " << size;
    }

    if(3 == size)
    {
        QByteArray cmdId = imageArray.mid(6,9);
        QDataStream commandId(cmdId);
        commandId >> m_dataSize;
        qDebug() << "汉子字节数" << size;
    }

    switch (size)
    {
    case 1:
        imageArray.remove(0,10);
        m_imagePackage.append(imageArray);
        break;

    case 2:
        imageArray.remove(0,10);
        m_imagePackage.append(imageArray);

        m_pix.loadFromData(m_imagePackage,"jpg");
        ui->lb_image->setPixmap(m_pix.scaled(595.2,792));  // 500 * 375
        break;

    case 3:
        imageArray.remove(0,10);
        imageArray.resize(m_dataSize);
        ui->textEdit->append(QTextCodec::codecForMib(106)->toUnicode(imageArray));
        break;

    default:
        break;
    }

}

这部分简单介绍下。识别对应命令ID,对对应的数据包处理。大家可以留意下QByteArray字节转换为整数的部分。这里面我没有对图像总的接收到的数据判断,大家具体情况具体处理。

(QTextCodec::codecForMib(106)->toUnicode(imageArray) 这个是对QByteArray转换为utf8编码的处理,最后得到的是中文。

最后看下结果图:

服务器接收---->>>

客户端发送--->>>

服务器我在windows下试过,接收数据处理不对,有机会我会再研究下的。

刚开始写这种图片组包的程序没什么经验,写出来是为了让更多刚接触编程的同志不再那么孤立无援!共勉!

你说的没错,我们公司就是做图中机械臂的~

本文分享自微信公众号 - Pou光明(pou0230)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-08-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • python pyqt5 窗体动画效果

    import sys from PyQt5.QtWidgets import QApplication, QWidget from PyQt5.QtGui ...

    用户5760343
  • python pyqt5 获得城市天气

    from PyQt5 import QtCore, QtGui, QtWidgets

    用户5760343
  • python pyqt5 加载QSS

    """ import sys from PyQt5.QtWidgets import QMainWindow, QApplication, QVBoxLay...

    用户5760343
  • python pyqt5 卡通人物形状窗体

    import sys from PyQt5.QtWidgets import QApplication, QWidget from PyQt5.QtGui ...

    用户5760343
  • python pyqt5 pandas处理数据

    """ Module implementing MainWindow. """

    用户5760343
  • python pyqt5 操作sqlite

    import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.Qt...

    用户5760343
  • python pyqt5 带分页表格

    import sys import re from PyQt5.QtWidgets import (QWidget, QHBoxLayout, QVBoxL...

    用户5760343
  • python pyqt5 matplotlib绘图

    import sys import random import matplotlib

    用户5760343
  • python pyqt5 按钮和label添加背景图片

    from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QVBoxLayout, QPushBut...

    用户5760343
  • 【Python进阶】Python进阶专栏、编程与开源框架知识星球上线,等你来follow

    大家好,今天我将在有三AI开设新专栏《Python进阶》。在这个专栏中,我们会讲述Python的各种进阶操作,包括Python对文件、数据的处理,Python各...

    用户1508658

扫码关注云+社区

领取腾讯云代金券