Qt Socket 收發圖片——影象拆包、組包、粘包處理
之前給大家分享了一個使用python發圖片資料、Qt server接收圖片的Demo。之前的Demo用於傳輸小位元組的圖片是可以的,但如果是傳輸大的圖片,使用socket無法一次完成傳送該怎麼辦呢?本次和大家分享一個對大的圖片拆包、組包、處理粘包的例子。
程式平臺:ubuntu 、 Qt 5.5.1
為了對接收到的影象位元組進行組包,我們需要對每包資料規定協議,協議如下圖:
每包資料前10個位元組對應含義如下:前兩個位元組對應資料包型別,中間四位元組預留,最後四位元組是包內資料實際長度。對應協議圖片更方便剛開始上手的兄弟理解。
對協議有了一個瞭解後,接下來說下程式結構。客戶端按照協議傳送圖片位元組,伺服器接收位元組,如果客戶端發多少伺服器就收多少那可真是太好了,然而意外總是如期而至。伺服器這邊由於socket的緩衝總是會粘包,所以伺服器這邊主要工作是拆包和組包,這也是整個程式組中最重要的部分。其次就是伺服器在接收圖片時為了響應更及時,單獨使用一個執行緒進行接收圖片,這裡面我使用的是Qt的moveToThread。也使用過linux的socket以及執行緒接收圖片,感覺效能要比Qt封裝過的要好,大家有需要的話可以在公眾號後臺留言。
接下來跟著程式走:
- 客戶端傳送部分:
①讀取圖片位元組
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