Qt Socket 传输图片——图像拆包、组包、粘包处理

  • 2019 年 10 月 3 日
  • 筆記

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

 

程序平台:ubuntu Qt 5.5.1

 

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

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

 

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

 

接下来跟着程序走:

 

  1. 客户端发送部分:

 

①读取图片字节

 

 1 void Widget::on_pbn_readPicture_clicked()   2 {   3     m_picturePath = m_picturePath +"/auboi5.jpg";   4     QPixmap pix;   5     bool ret = pix.load(m_picturePath);   6   7     QBuffer buffer;   8     buffer.open(QIODevice::ReadWrite);   9     bool ret2 = pix.save(&buffer,"jpg");  10  11     m_pictureByteArray = buffer.data();  12  13     if(ret2)  14     {  15         QString str = "read image finish!";  16         ui->textEdit->append(str);  17     }  18 }

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

 

②发送图像拆包

 1 QByteArray dataPackage;   2   3     // command 0 ,package total size   4     QDataStream dataHead(&dataPackage,QIODevice::ReadWrite);   5     dataHead << quint16(0);   6     dataHead << quint32(0);   7     dataHead << quint32(m_pictureByteArray.size());   8     dataPackage.resize(40960);   9     mp_clsTcpSocket->write(dataPackage);  10     dataPackage.clear();  11  12     QThread::msleep(20);

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

 

③发送utf8 编码的中文

 

 1 void Widget::on_pbn_sendChinese_clicked()   2 {   3     QByteArray dataPackage;   4     QByteArray chinese = "阶级终极形态假设!";   5   6     //command 3   7     QDataStream dataTail(&dataPackage,QIODevice::ReadWrite);   8     dataTail << quint16(3);   9     dataTail << quint32(0);  10     dataTail << quint32(chinese.size());  11  12     dataPackage = dataPackage.insert(10,chinese.data(),chinese.size());  13     dataPackage.resize(40960);  14  15     mp_clsTcpSocket->write(dataPackage);  16 }

 

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

 

 

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

 

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

 

 1 void TcpServerRecvImage::slot_readClientData()   2 {   3     QByteArray buffer;   4     buffer = mp_clsTcpClientConnnect->readAll();   5   6     m_bufferSize = buffer.size();   7     m_total = m_total + buffer.size();   8     qDebug() << "socket Receive Data size:" << m_bufferSize << m_total;   9  10     if(m_bufferSize == 40960)  11     {  12         emit signal_sendImagedataPackage(buffer);  13         qDebug() << "直接发送";  14         return;  15     }  16  17  18     if((m_picture.size() + m_bufferSize) == 40960)  19     {  20         m_picture.append(buffer);  21  22         emit signal_sendImagedataPackage(m_picture);  23         m_picture.clear();  24         qDebug() << "拼接后40960";  25         return;  26     }  27  28  29     if((m_picture.size() + m_bufferSize) < 40960)  30     {  31         m_picture.append(buffer) ;  32         qDebug() << "直接拼接";  33         return;  34     }  35  36     if((m_picture.size() + m_bufferSize) > 40960)  37     {  38         //case one  39         if((m_bufferSize > 40960) && (m_picture.size() == 0))  40         {  41             while(m_bufferSize/40960)  42             {  43                 QByteArray data = buffer.left(40960);  44                 buffer.remove(0,40960);  45  46                 emit signal_sendImagedataPackage(data);  47                 m_bufferSize = buffer.size();  48  49                 if((m_bufferSize/40960 == 0) && (m_bufferSize!=0))  50                 {  51                     m_picture.append(buffer);  52                 }  53                 QThread::msleep(2);  54             }  55             return;  56         }  57  58         //case two  59         if((m_bufferSize > 40960) && (m_picture.size() > 0))  60         {  61             int frontLength = 40960 - m_picture.size();  62             QByteArray data = buffer.left(frontLength);  63             buffer.remove(0,frontLength);  64  65             m_picture.append(data);  66             if(40960 == m_picture.size())  67             {  68                 emit signal_sendImagedataPackage(m_picture);  69                 m_picture.clear();  70             }  71  72             m_bufferSize = buffer.size();  73  74             while(m_bufferSize/40960)  75             {  76                 QByteArray data = buffer.left(40960);  77                 buffer.remove(0,40960);  78  79                 emit signal_sendImagedataPackage(data);  80                 m_bufferSize = buffer.size();  81  82                 if((m_bufferSize/40960 == 0) && (m_bufferSize!=0))  83                 {  84                     m_picture.append(buffer);  85                 }  86                 QThread::msleep(2);  87             }  88             return;  89         }  90     }  91 }

 

 

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

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数据包

 1 void Widget::slot_imagePackage(QByteArray imageArray)   2 {   3     m_imageCount++;   4     QString number = QString::number(m_imageCount);   5     ui->textEdit->append(number);   6   7     QByteArray cmdId = imageArray.left(2);   8     QDataStream commandId(cmdId);   9     quint16 size;  10     commandId >> size;  11  12     if(0 == size)  13     {  14         QByteArray cmdId = imageArray.mid(6,9);  15         QDataStream commandId(cmdId);  16         quint32 size;  17         commandId >> size;  18         qDebug() << "图片的总字节数" << size;  19     }  20  21     if(2 == size)  22     {  23         QByteArray cmdId = imageArray.mid(6,9);  24         QDataStream commandId(cmdId);  25         quint32 size;  26         commandId >> size;  27         qDebug() << "图片包尾字节数 " << size;  28     }  29  30     if(3 == size)  31     {  32         QByteArray cmdId = imageArray.mid(6,9);  33         QDataStream commandId(cmdId);  34         commandId >> m_dataSize;  35         qDebug() << "汉子字节数" << size;  36     }  37  38     switch (size)  39     {  40     case 1:  41         imageArray.remove(0,10);  42         m_imagePackage.append(imageArray);  43         break;  44  45     case 2:  46         imageArray.remove(0,10);  47         m_imagePackage.append(imageArray);  48  49         m_pix.loadFromData(m_imagePackage,"jpg");  50         ui->lb_image->setPixmap(m_pix.scaled(595.2,792));  // 500 * 375  51         break;  52  53     case 3:  54         imageArray.remove(0,10);  55         imageArray.resize(m_dataSize);  56         ui->textEdit->append(QTextCodec::codecForMib(106)->toUnicode(imageArray));  57         break;  58  59     default:  60         break;  61     }  62  63 }

 

这部分简单介绍下。识别对应命令ID,对对应的数据包处理。这里面我没有对图像总的接收到的数据判断,大家具体情况具体处理。

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

 

最后看下结果图:

服务器接收—->>>

 

 

客户端发送—>>>

 

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

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

 

需要整个工程的公众号后台留言~