之前给大家分享了一个使用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下试过,接收数据处理不对,有机会我会再研究下的。
刚开始写这种图片组包的程序没什么经验,写出来是为了让更多刚接触编程的同志不再那么孤立无援!共勉!
需要整个工程的公众号后台留言~