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下試過,接收數據處理不對,有機會我會再研究下的。

剛開始寫這種圖片組包的程序沒什麼經驗,寫出來是爲了讓更多剛接觸編程的同志不再那麼孤立無援!共勉!

 

需要整個工程的公衆號後臺留言~

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章