70

Qt Socket 收发图片——图像拆包、组包、粘包处理(二)

 4 years ago
source link: https://www.tuicool.com/articles/FzU7reI
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

之前给大家分享了一个使用python发图片数据、 Qt server 接收图片的 Demo 。之前的 Demo 用于传输小字节的图片是可以的,但如果是传输大的图片,使用 socket 无法一次完成发送该怎么办呢?本次和大家分享一个对大的图片拆包、组包、处理粘包的例子。

程序平台:ubuntu 、 Qt 5.5.1

为了对接收到的图像字节进行组包,我们需要对每包数据规定协议,协议如下图:

f2Ij6ni.jpg!web

每包数据前10个字节对应含义如下:前两个字节对应数据包类型,中间四字节预留,最后四字节是包内数据实际长度。对应协议图片更方便刚开始上手的兄弟理解。

对协议有了一个了解后,接下来说下程序结构。客户端按照协议发送图片字节,服务器接收字节,如果客户端发多少服务器就收多少那可真是太好了,然而意外总是如期而至。服务器这边由于socket的缓冲总是会粘包,所以服务器这边主要工作是拆包和组包,这也是整个程序组中最重要的部分。其次就是服务器在接收图片时为了响应更及时,单独使用一个线程进行接收图片,这里面我使用的是 QtmoveToThread 。也使用过 linuxsocket 以及线程接收图片,感觉性能要比 Qt 封装过的要好,大家有需要的话可以在公众号后台留言。

接下来跟着程序走:

1.  客户端发送部分:

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_picture ByteArray 中,成功后在 textEdit 显示 read image finish!

②发送图像拆包

QByteArray dataPackage;


// command 0 ,package total size

QDataStream dataHead(&dataPackage,QIODevice::ReadWrite);

dataHead << quint16(0);

dataHead << quint32(0);

dataHead << quint32(m_pictureByteArray.size());

dataPackage.resize(40960);

mp_clsTcpSocket->write(dataPackage);

dataPackage.clear();


QThread::msleep(20);

这里我拿一包数据举例说明。第一包数据是将读取到的整张图片的大小发送出去,以判断接收方接收到的数据是否完整。主要涉及到 Qt 一些数据类型的转换,如将整型字节存入 QByteArray 中使用 QDataStream 。之后将数据包大小重新设置为 40960 ,方便服务器处理粘包。

③发送 utf8  编码的中文

void Widget::on_pbn_sendChinese_clicked()

{

QByteArray dataPackage;

QByteArray chinese = "阶级终极形态假设!";


//command 3

QDataStream dataTail(&dataPackage,QIODevice::ReadWrite);

dataTail << quint16(3);

dataTail << quint32(0);

dataTail << quint32(chinese.size());


dataPackage = dataPackage.insert(10,chinese.data(),chinese.size());

dataPackage.resize(40960);


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 == 40960)

{

emit signal_sendImagedataPackage(buffer);

qDebug() << "直接发送";

return;

}



if((m_picture.size() + m_bufferSize) == 40960)

{

m_picture.append(buffer);


emit signal_sendImagedataPackage(m_picture);

m_picture.clear();

qDebug() << "拼接后40960";

return;

}



if((m_picture.size() + m_bufferSize) < 40960)

{

m_picture.append(buffer) ;

qDebug() << "直接拼接";

return;

}


if((m_picture.size() + m_bufferSize) > 40960)

{

//case one

if((m_bufferSize > 40960) && (m_picture.size() == 0))

{

while(m_bufferSize/40960)

{

QByteArray data = buffer.left(40960);

buffer.remove(0,40960);


emit signal_sendImagedataPackage(data);

m_bufferSize = buffer.size();


if((m_bufferSize/40960 == 0) && (m_bufferSize!=0))

{

m_picture.append(buffer);

}

QThread::msleep(2);

}

return;

}


//case two

if((m_bufferSize > 40960) && (m_picture.size() > 0))

{

int frontLength = 40960 - m_picture.size();

QByteArray data = buffer.left(frontLength);

buffer.remove(0,frontLength);


m_picture.append(data);

if(40960 == m_picture.size())

{

emit signal_sendImagedataPackage(m_picture);

m_picture.clear();

}


m_bufferSize = buffer.size();


while(m_bufferSize/40960)

{

QByteArray data = buffer.left(40960);

buffer.remove(0,40960);


emit signal_sendImagedataPackage(data);

m_bufferSize = buffer.size();


if((m_bufferSize/40960 == 0) && (m_bufferSize!=0))

{

m_picture.append(buffer);

}

QThread::msleep(2);

}

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(2);

QDataStream commandId(cmdId);

quint16 size;

commandId >> size;


if(0 == size)

{

QByteArray cmdId = imageArray.mid(6,9);

QDataStream commandId(cmdId);

quint32 size;

commandId >> size;

qDebug() << "图片的总字节数" << size;

}


if(2 == size)

{

QByteArray cmdId = imageArray.mid(6,9);

QDataStream commandId(cmdId);

quint32 size;

commandId >> size;

qDebug() << "图片包尾字节数 " << size;

}


if(3 == size)

{

QByteArray cmdId = imageArray.mid(6,9);

QDataStream commandId(cmdId);

commandId >> m_dataSize;

qDebug() << "汉子字节数" << size;

}


switch (size)

{

case 1:

imageArray.remove(0,10);

m_imagePackage.append(imageArray);

break;


case 2:

imageArray.remove(0,10);

m_imagePackage.append(imageArray);


m_pix.loadFromData(m_imagePackage,"jpg");

ui->lb_image->setPixmap(m_pix.scaled(595.2,792)); // 500 * 375

break;


case 3:

imageArray.remove(0,10);

imageArray.resize(m_dataSize);

ui->textEdit->append(QTextCodec::codecForMib(106)->toUnicode(imageArray));

break;


default:

break;

}


}

这部分简单介绍下。识别对应命令ID,对对应的数据包处理。大家可以留意下QByteArray字节转换为整数的部分。这里面我没有对图像总的接收到的数据判断,大家具体情况具体处理。

(QTextCodec::codecForMib(106)->toUnicode(imageArray)   这个是对 QByteArray 转换为 utf8 编码的处理,最后得到的是中文。

最后看下结果图:

服务器接收---->>>

JBNvea6.jpg!web

客户端发送--->>>

Iz2UZrJ.jpg!web

服务器我在windows下试过,接收数据处理不对,有机会我会再研究下的。

刚开始写这种图片组包的程序没什么经验,写出来是为了让更多刚接触编程的同志不再那么孤立无援!共勉!

你说的没错,我们公司就是做图中机械臂的~

eIZJ3iQ.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK