1. 程式人生 > >Qt Socket 收發圖片——影象拆包、組包、粘包處理

Qt Socket 收發圖片——影象拆包、組包、粘包處理

之前給大家分享了一個使用python發圖片資料、Qt server接收圖片的Demo。之前的Demo用於傳輸小位元組的圖片是可以的,但如果是傳輸大的圖片,使用socket無法一次完成傳送該怎麼辦呢?本次和大家分享一個對大的圖片拆包、組包、處理粘包的例子。

 

程式平臺:ubuntu 、 Qt 5.5.1

 

為了對接收到的影象位元組進行組包,我們需要對每包資料規定協議,協議如下圖:

每包資料前10個位元組對應含義如下:前兩個位元組對應資料包型別,中間四位元組預留,最後四位元組是包內資料實際長度。對應協議圖片更方便剛開始上手的兄弟理解。

 

對協議有了一個瞭解後,接下來說下程式結構。客戶端按照協議傳送圖片位元組,伺服器接收位元組,如果客戶端發多少伺服器就收多少那可真是太好了,然而意外總是如期而至。伺服器這邊由於socket的緩衝總是會粘包,所以伺服器這邊主要工作是拆包和組包,這也是整個程式組中最重要的部分。其次就是伺服器在接收圖片時為了響應更及時,單獨使用一個執行緒進行接收圖片,這裡面我使用的是Qt的moveToThread。也使用過linux的socket以及執行緒接收圖片,感覺效能要比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 }

讀取圖片位元組主要用到了Qt的QPixmap 類,這個不細說,大傢俱體可參考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下試過,接收資料處理不對,有機會我會再研究下的。

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

 

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

 

&n