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

程序平台:ubuntu Qt 5.5.1

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

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

每包数据前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编码的处理,最后得到的是中文。

最后看下结果图:

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

客户端发送--->>>

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

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

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

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

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

08-07 03:47