Qt Socket 收发图片——图像拆包组包粘包处理

Posted d-h-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt Socket 收发图片——图像拆包组包粘包处理相关的知识,希望对你有一定的参考价值。

之前给大家分享了一个使用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下试过,接收数据处理不对,有机会我会再研究下的。

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

 

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

 

技术图片

 

以上是关于Qt Socket 收发图片——图像拆包组包粘包处理的主要内容,如果未能解决你的问题,请参考以下文章

Netty——解决TCP粘包、拆包

TCP-缓冲区和粘包、拆包有啥关系?

TCP的组包半包粘包与分包

如何解决TCP拆包粘包问题

TCP粘包和拆包

netty之粘包拆包ByteToMessageDecoder