Qt Socket 收发图片——图像拆包、组包、粘包处理
之前给大家分享了一个使用python发图片数据、Qt server接收图片的Demo。之前的Demo用于传输小字节的图片是可以的,但如果是传输大的图片,使用socket无法一次完成发送该怎么办呢?本次和大家分享一个对大的图片拆包、组包、处理粘包的例子。
程序平台:ubuntu 、 Qt 5.5.1
为了对接收到的图像字节进行组包,我们需要对每包数据规定协议,协议如下图:
每包数据前10个字节对应含义如下:前两个字节对应数据包类型,中间四字节预留,最后四字节是包内数据实际长度。对应协议图片更方便刚开始上手的兄弟理解。
对协议有了一个了解后,接下来说下程序结构。客户端按照协议发送图片字节,服务器接收字节,如果客户端发多少服务器就收多少那可真是太好了,然而意外总是如期而至。服务器这边由于socket的缓冲总是会粘包,所以服务器这边主要工作是拆包和组包,这也是整个程序组中最重要的部分。其次就是服务器在接收图片时为了响应更及时,单独使用一个线程进行接收图片,这里面我使用的是Qt的moveToThread。也使用过linux的socket以及线程接收图片,感觉性能要比Qt封装过的要好,大家有需要的话可以在公众号后台留言。
接下来跟着程序走:
- 客户端发送部分:
①读取图片字节
void Widget::on_pbn_readPicture_clicked()
{
m_picturePath = m_picturePath +"/auboi5.jpg";
QPixmap pix;
bool ret = pix.load(m_picturePath); QBuffer buffer;
buffer.open(QIODevice::ReadWrite);
bool ret2 = pix.save(&buffer,"jpg"); m_pictureByteArray = buffer.data(); if(ret2)
{
QString str = "read image finish!";
ui->textEdit->append(str);
}
}
读取图片字节主要用到了Qt的QPixmap 类,这个不细说,大家具体可参考Qt文档。图片字节被读取到m_pictureByteArray中,成功后在textEdit显示read image finish!。
②发送图像拆包
QByteArray dataPackage; // command 0 ,package total size
QDataStream dataHead(&dataPackage,QIODevice::ReadWrite);
dataHead << quint16();
dataHead << quint32();
dataHead << quint32(m_pictureByteArray.size());
dataPackage.resize();
mp_clsTcpSocket->write(dataPackage);
dataPackage.clear(); QThread::msleep();
这里我拿医一包数据举例说明。第一包数据是将读取到的整张图片的大小发送出去,以判断接收方接收到的数据是否完整。主要涉及到Qt一些数据类型的转换,如将整型字节存入QByteArray 中使用QDataStream 。之后将数据包大小重新设置为40960,方便服务器处理粘包。
③发送utf8 编码的中文
void Widget::on_pbn_sendChinese_clicked()
{
QByteArray dataPackage;
QByteArray chinese = "阶级终极形态假设!"; //command 3
QDataStream dataTail(&dataPackage,QIODevice::ReadWrite);
dataTail << quint16();
dataTail << quint32();
dataTail << quint32(chinese.size()); dataPackage = dataPackage.insert(,chinese.data(),chinese.size());
dataPackage.resize(); mp_clsTcpSocket->write(dataPackage);
}
这部分直接略过了,大家参考下即可。
2.服务器接收部分(重要):
①线程中槽函数接收图片数据拆包
void TcpServerRecvImage::slot_readClientData()
{
QByteArray buffer;
buffer = mp_clsTcpClientConnnect->readAll(); m_bufferSize = buffer.size();
m_total = m_total + buffer.size();
qDebug() << "socket Receive Data size:" << m_bufferSize << m_total; if(m_bufferSize == )
{
emit signal_sendImagedataPackage(buffer);
qDebug() << "直接发送";
return;
} if((m_picture.size() + m_bufferSize) == )
{
m_picture.append(buffer); emit signal_sendImagedataPackage(m_picture);
m_picture.clear();
qDebug() << "拼接后40960";
return;
} if((m_picture.size() + m_bufferSize) < )
{
m_picture.append(buffer) ;
qDebug() << "直接拼接";
return;
} if((m_picture.size() + m_bufferSize) > )
{
//case one
if((m_bufferSize > ) && (m_picture.size() == ))
{
while(m_bufferSize/)
{
QByteArray data = buffer.left();
buffer.remove(,); emit signal_sendImagedataPackage(data);
m_bufferSize = buffer.size(); if((m_bufferSize/ == ) && (m_bufferSize!=))
{
m_picture.append(buffer);
}
QThread::msleep();
}
return;
} //case two
if((m_bufferSize > ) && (m_picture.size() > ))
{
int frontLength = - m_picture.size();
QByteArray data = buffer.left(frontLength);
buffer.remove(,frontLength); m_picture.append(data);
if( == m_picture.size())
{
emit signal_sendImagedataPackage(m_picture);
m_picture.clear();
} m_bufferSize = buffer.size(); while(m_bufferSize/)
{
QByteArray data = buffer.left();
buffer.remove(,); emit signal_sendImagedataPackage(data);
m_bufferSize = buffer.size(); if((m_bufferSize/ == ) && (m_bufferSize!=))
{
m_picture.append(buffer);
}
QThread::msleep();
}
return;
}
}
}
程序有那么一点长,我先说下他们在做的事情:
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数据包
void Widget::slot_imagePackage(QByteArray imageArray)
{
m_imageCount++;
QString number = QString::number(m_imageCount);
ui->textEdit->append(number); QByteArray cmdId = imageArray.left();
QDataStream commandId(cmdId);
quint16 size;
commandId >> size; if( == size)
{
QByteArray cmdId = imageArray.mid(,);
QDataStream commandId(cmdId);
quint32 size;
commandId >> size;
qDebug() << "图片的总字节数" << size;
} if( == size)
{
QByteArray cmdId = imageArray.mid(,);
QDataStream commandId(cmdId);
quint32 size;
commandId >> size;
qDebug() << "图片包尾字节数 " << size;
} if( == size)
{
QByteArray cmdId = imageArray.mid(,);
QDataStream commandId(cmdId);
commandId >> m_dataSize;
qDebug() << "汉子字节数" << size;
} switch (size)
{
case :
imageArray.remove(,);
m_imagePackage.append(imageArray);
break; case :
imageArray.remove(,);
m_imagePackage.append(imageArray); m_pix.loadFromData(m_imagePackage,"jpg");
ui->lb_image->setPixmap(m_pix.scaled(595.2,)); // 500 * 375
break; case :
imageArray.remove(,);
imageArray.resize(m_dataSize);
ui->textEdit->append(QTextCodec::codecForMib()->toUnicode(imageArray));
break; default:
break;
} }
这部分简单介绍下。识别对应命令ID,对对应的数据包处理。这里面我没有对图像总的接收到的数据判断,大家具体情况具体处理。
(QTextCodec::codecForMib(106)->toUnicode(imageArray) 这个是对QByteArray转换为utf8编码的处理,最后得到的是中文。
最后看下结果图:
服务器接收---->>>
客户端发送--->>>
服务器我在windows下试过,接收数据处理不对,有机会我会再研究下的。
刚开始写这种图片组包的程序没什么经验,写出来是为了让更多刚接触编程的同志不再那么孤立无援!共勉!
需要整个工程的公众号后台留言~
Qt Socket 收发图片——图像拆包、组包、粘包处理的更多相关文章
- [转]java nio解决半包 粘包问题
java nio解决半包 粘包问题 NIO socket是非阻塞的通讯模式,与IO阻塞式的通讯不同点在于NIO的数据要通过channel放到一个缓存池ByteBuffer中,然后再从这个缓存池中读出数 ...
- python socket的应用 以及tcp中的粘包现象
1,socket套接字 一个接口模块,在tcp/udp协议之间的传输接口,将其影藏在socket之后,用户看到的是socket让其看到的. 在tcp中当做server和client的主要模块运用 #s ...
- Unity C# 自定义TCP传输协议以及封包拆包、解决粘包问题
本文只是初步实现了一个简单的TCP自定协议,更为复杂的协议可以根据这种方式去扩展. TCP协议,通俗一点的讲,它是一种基于socket传输的由发送方和接收方事先协商好的一种消息包组成结构,主要由消息头 ...
- netty10---分包粘包
客户端:根据 长度+数据 方式发送 package com.server; import java.net.Socket; import java.nio.ByteBuffer; public cla ...
- 网络编程3 网络编程之缓冲区&subprocess&粘包&粘包解决方案
1.sub简单使用 2.粘包现象(1) 3.粘包现象(2) 4.粘包现象解决方案 5.struct学习 6.粘包现象升级版解决方案 7.打印进度条
- goim socket丢包粘包问题解决。
-(NSInteger)bytesToInt:(unsigned char*) data { return (data[3]&255)|(data[2]&255)<<8|( ...
- mina框架tcpt通讯接收数据断包粘包处理
用mina做基于tcp,udp有通讯有段时间了,一直对编码解码不是很熟悉,这次做项目的时候碰到了断包情况,贴一下解决过程, 我接受数据格式如下图所示: unit32为c++中数据类型,代表4个字节,由 ...
- python--socket粘包
socket粘包 1 什么是粘包 须知:只有TCP有粘包现象,UDP永远不会粘包,首先需要掌握一个socket收发消息的原理, 所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少 ...
- TCP粘包/拆包问题
无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制. TCP粘包/拆包 TCP是个"流"协议,所谓流,就是没有界限的一串数据.大家可以想想河 ...
随机推荐
- 可用的NTP服务器地址
国内可用的Internet时间同步服务器地址(NTP时间服务器)好在阿里云提供了7个NTP时间服务器也就是Internet时间同步服务器地址 ntp1.aliyun.comntp2.aliyun.co ...
- 从无到有构建vue实战项目(二)
二.vue项目的初步搭建 该项目我采用了当下最流行的vue ui框架---element-ui,首先用vue-cli构建一个vue项目: vue create education 然后会出现一系列配置 ...
- Spring 核心技术(3)
接上篇:Spring 核心技术(2) version 5.1.8.RELEASE 1.4 依赖 典型的企业应用程序不会只包含单个对象(或 Spring 术语中的 bean).即使是最简单的应用程序也是 ...
- Impala集成C3P0的连接方式
1. 概述 Impala是Cloudera公司主导开发的新型查询系统,它提供SQL语义,能查询存储在Hadoop的HDFS和HBase中的PB级大数据.已有的Hive系统虽然也提供了SQL语义,但由于 ...
- 5. xadmin 后台搭建
要维护他人产权,就不喽了,直接飞机 Django1.11.11使用xadmin的方法(一: 快速安装篇):https://www.jianshu.com/p/bcb74595213e Django1. ...
- reportlab生成pdf
文档地址:https://www.reportlab.com/docs/reportlab-userguide.pdf 源码地址:https://bitbucket.org/rptlab/report ...
- JAVA包装类解析和面试陷阱分析
包装类 什么是包装类 虽然 Java 语言是典型的面向对象编程语言,但其中的八种基本数据类型并不支持面向对象编程,基本类型的数据不具备“对象”的特性——不携带属性.没有方法可调用. 沿用它们只是为了迎 ...
- spark 源码分析之十六 -- Spark内存存储剖析
上篇spark 源码分析之十五 -- Spark内存管理剖析 讲解了Spark的内存管理机制,主要是MemoryManager的内容.跟Spark的内存管理机制最密切相关的就是内存存储,本篇文章主要介 ...
- bzoj2431 || 洛谷P1521 求逆序对
考虑一下插⼊法 n<=100n<=100n<=100 f[i][j]f[i][j]f[i][j]表⽰111~iii的全排列有j个逆序对的⽅案数 f[i][j]=Σf[i−1][j−k ...
- 个人永久性免费-Excel催化剂功能第50波-批量打印、导出PDF、双面打印功能
在倡导无纸化办公的今天,是否打印是一个碍眼的功能呢,某些时候的确是,但对于数据的留存,在现在鼓吹区块链技术的今天,仍然不失它的核心价值,数据报表.单据打印出来留存,仍然是一种不可或缺的数据存档和防篡改 ...