Qt使用UDP协议传输数据(分包,组包,GZIP压缩数据)

Image description

这篇文章主要的目的是讲UDP传输的字节限制以及用分包和组包去解决这个限制,本文直接将桌面截屏后传输是低效的做法,使用H.264或H.265对图像进行处理能减少传输的数据量。

https://blog.csdn.net/m0_64113647/article/details/127025160

本文不涉及重传机制,当数据块丢失时,组包无法完成,所以不推荐用在完整性要求高的场景。如果希望数据能够安全完整的传输,推荐使用TCP协议。

分包与组包

数据报的长度是指包括报头和数据部分在内的总字节数。因为报头的长度是固定的,所以该域主要被用来计算可变长度的数据部分(又称为数据负载)。数据报的最大长度根据操作环境的不同而各异。从理论上说,包含报头在内的数据报的最大长度为65535字节。不过,一些实际应用往往会限制数据报的大小,有时会降低到8192字节。

为了避免发送文件时udp报文超出最大长度限制,我们会把文件切割成更小的块,每个块都会用对应的序号和偏移地址(在组包时防止顺序混乱),并且使用批号作为键,将同一批号的块根据序号进行组装,接收时判断序号与总块数判断当前文件是否接收完成。

包头定义
#ifndef PACKAGE_H
#define PACKAGE_H

#define MAX_PACK_SIZE 1024

#pragma pack(1)
typedef struct _PACKAGE_HEAD {
    char magicNumber[2];            // 魔术字
    unsigned int BN;                // 批号
    unsigned int packageHeadSize;   // 包头大小
    unsigned int packageSize;       // 总大小
    unsigned int packageTotal;      // 分包总数量
    unsigned int packageCurIdx;     // 当前分包索引
    unsigned int fileSize;          // 文件总大小
} PACKAGE_HEAD;
#pragma pack()

#endif // PACKAGE_H
GZIP压缩

HTTP协议上的GZIP编码是一种用来改进WEB应用程序性能的技术。大流量的WEB站点常常使用GZIP压缩技术来让用户感受更快的速度。这一般是指WWW服务器中安装的一个功能,当有人来访问这个服务器中的网站时,服务器中的这个功能就将网页内容压缩后传输到来访的电脑浏览器中显示出来.一般对纯文本内容可压缩到原大小的40%.这样传输就快了,效果就是你点击网址后会很快的显示出来.当然这也会增加服务器的负载. 一般服务器中都安装有这个功能模块的。

为了提高传输速度,减少带宽,使用GZIP压缩后进行传输是个不错的选择。

安装zlib

  • 去官网下载最新版的zlib: http://www.zlib.net/

  • 解压并用命令行工具进入该目录

  • 执行脚本配置工具:./configure

  • 编译并安装:make && make install

Qt引入zlib

在.pro文件中新增:LIBS += -L/usr/local/lib -lz,路径请使用实际安装路径。

压缩与解压类

#ifndef ZIP_H
#define ZIP_H
#include <zlib.h>
#include <zconf.h>

#include <QObject>

class Zip : public QObject
{
    Q_OBJECT
public:
// 压缩
static QByteArray GzipCompress(const QByteArray& postBody) {
    QByteArray outBuf;
    z_stream c_stream;
    int err = 0;
    int windowBits = 15;
    int GZIP_ENCODING = 16;
    if (!postBody.isEmpty()) {
        c_stream.zalloc = (alloc_func)0;
        c_stream.zfree = (free_func)0;
        c_stream.opaque = (voidpf)0;
        c_stream.next_in = (Bytef *)postBody.data();
        c_stream.avail_in = postBody.size();
        if (deflateInit2(&c_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
            MAX_WBITS + GZIP_ENCODING, 8, Z_DEFAULT_STRATEGY) != Z_OK) return QByteArray();
        for (;;) {
            char destBuf[4096] = { 0 };
            c_stream.next_out = (Bytef *)destBuf;
            c_stream.avail_out = 4096;
            int err = deflate(&c_stream, Z_FINISH);
            outBuf.append(destBuf, 4096 - c_stream.avail_out);
            if (err == Z_STREAM_END || err != Z_OK) {
                break;
            }
        }
        auto total = c_stream.total_out;
        deflateEnd(&c_stream);
        total = c_stream.total_out;
    }
    return outBuf;
}

// 解压
static QByteArray GZipUnCompress(const QByteArray& src) {
    QByteArray outBuffer;
    z_stream strm;
    strm.zalloc = NULL;
    strm.zfree = NULL;
    strm.opaque = NULL;

    strm.avail_in = src.size();
    strm.next_in = (Bytef *)src.data();

    int err = -1, ret = -1;
    err = inflateInit2(&strm, MAX_WBITS + 16);
    if (err == Z_OK) {
        while (true) {
            char buffer[4096] = { 0 };
            strm.avail_out = 4096;
            strm.next_out = (Bytef *)buffer;
            int code = inflate(&strm, Z_FINISH);
            outBuffer.append(buffer, 4096 - strm.avail_out);
            if (Z_STREAM_END == code ) {
                break;
            }
        }
    }
    inflateEnd(&strm);
    return outBuffer;
}

private:
    explicit Zip(QObject *parent = nullptr);

signals:

};

#endif // ZIP_H
分包
unsigned int BN = 0;    // 初始化批号
QScreen *screen = QGuiApplication::primaryScreen();
QRect mm = screen->availableGeometry() ;

while (true) {
    QByteArray ba;
    QBuffer bf(&ba);
    screen->grabWindow(0, 0, 0, mm.width(), mm.height()).save(&bf, "jpg", 50);
    // 压缩数据
    ba = Zip::GzipCompress(ba);

    PACKAGE_HEAD head;
    // 定义魔术字
    head.magicNumber[0] = 'G';
    head.magicNumber[1] = 'K';
    // 获取并自增批号
    head.BN = BN++;
    head.packageHeadSize = sizeof(PACKAGE_HEAD);
    head.fileSize = ba.size();

    // 计算总分包数量
    head.packageTotal = head.fileSize / MAX_PACK_SIZE;
    head.packageTotal += head.fileSize % MAX_PACK_SIZE > 0 ? 1 : 0;

    for (int i=0; i<head.packageTotal; i++) {
        QByteArray b;
        head.packageCurIdx = i;
        if (head.fileSize - i * MAX_PACK_SIZE >= MAX_PACK_SIZE) {
            head.packageSize = MAX_PACK_SIZE + sizeof(PACKAGE_HEAD);
            b.append((char*)&head, sizeof(head));
            b.append(ba.mid(i * MAX_PACK_SIZE, MAX_PACK_SIZE));
        } else {
            head.packageSize = head.fileSize - i * MAX_PACK_SIZE + sizeof(PACKAGE_HEAD);
            b.append((char*)&head, sizeof(head));
            b.append(ba.mid(i * MAX_PACK_SIZE, head.fileSize - i * MAX_PACK_SIZE));
        }
        QtConcurrent::run([=](){
            // 使用udp发送分包数据
            QUdpSocket cli;
            cli.writeDatagram(b, QHostAddress("127.0.0.1"), 8080);
        });

    }
}

组包

// 定义缓冲区结构体用于管理封包
typedef struct _PACKAGE {
    unsigned int idxCount;
    QByteArray buf;
} PACKAGE;

QUdpSocket* m_socket;                   // UDP套接字
QMutex m_mutex;                         // 锁
QMap<unsigned int, PACKAGE> m_buf;      // 接收缓冲区

// 收包函数
receive() {
    QMutexLocker _(&m_mutex);
    QByteArray ba;
    while(m_socket->hasPendingDatagrams()) {
        ba.resize(m_socket->pendingDatagramSize());
        m_socket->readDatagram(ba.data(), ba.size());

        if (ba.size() <= sizeof(PACKAGE_HEAD)) {
            qDebug() << "ba.size() <= sizeof(PACKAGE_HEAD)";
            m_buf.clear();
            return;
        }
        PACKAGE_HEAD head;
        memcpy((char*)&head, ba.constData(), sizeof(PACKAGE_HEAD));
        if (head.magicNumber[0] != 'G' || head.magicNumber[1] != 'K') {
            qDebug() << "magicNumber error";
            return;
        }
        if (!m_buf.contains(head.BN)) {
            m_buf[head.BN] = PACKAGE{0, QByteArray()};
            m_buf[head.BN].buf.fill(0, head.fileSize);
        }
        m_buf[head.BN].idxCount++;
        // 通过当前索引 * 分包大小计算出当前块所在文件的偏移值,并将数据填充进去
        memcpy(m_buf[head.BN].buf.data() + head.packageCurIdx * MAX_PACK_SIZE, ba.constData() + sizeof(PACKAGE_HEAD), head.packageSize - head.packageHeadSize);

        // 如果当前批号的索引计数值等于总包数,证明该批号的数据传输并组装完成
        if (m_buf[head.BN].idxCount == head.packageTotal) {
            // 解压数据
            ba = Zip::GZipUnCompress(m_buf[head.BN].buf);
            QPixmap pixmap;
            pixmap.loadFromData(ba);

            QPalette palette;
            palette.setBrush(backgroundRole(), QBrush(pixmap.scaled(this->size(), Qt::KeepAspectRatioByExpanding)));
            setPalette(palette);
            m_buf.remove(head.BN);
            // 检查缓冲中还有多少批号未完成
            qDebug() << m_buf.size();
        }

    }
}
  • 4
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt中解析UDP协议包可以使用QUdpSocket类,该类提供了读取和写入UDP数据报的方法。以下是一个简单的示例,演示如何使用QUdpSocket类解析UDP数据报: ```cpp void MyObject::readPendingDatagrams() { while (udpSocket->hasPendingDatagrams()) { QByteArray datagram; datagram.resize(udpSocket->pendingDatagramSize()); QHostAddress sender; quint16 senderPort; udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); // 在这里对数据报进行解析 // datagram.data()指向数据报的首字节 // datagram.size()是数据报的大小(字节数) // sender是发送方的IP地址 // senderPort是发送方的端口号 } } ``` 在上述示例中,`udpSocket`是一个QUdpSocket对象,它的`readPendingDatagrams()`方法用于读取所有待处理的数据报。如果没有待处理的数据报,该方法会立即返回。 在while循环中,我们使用`udpSocket`对象的`hasPendingDatagrams()`方法检查是否有待处理的数据报。如果有,我们先创建一个QByteArray对象`datagram`,它的大小等于数据报的大小,然后调用`udpSocket`的`readDatagram()`方法读取数据报。`readDatagram()`方法的第一个参数是指向数据数据的指针,第二个参数是数据报的大小,第三个参数是发送方的IP地址,第四个参数是发送方的端口号。 在`readPendingDatagrams()`函数中,我们可以对数据报进行解析。`datagram.data()`指向数据报的首字节,`datagram.size()`是数据报的大小(字节数),`sender`是发送方的IP地址,`senderPort`是发送方的端口号。 除了使用`readDatagram()`方法读取数据报之外,还可以使用`waitForReadyRead()`方法等待数据报的到来,或者使用`bind()`方法将`QUdpSocket`对象绑定到特定的IP地址和端口号上,以便在该地址和端口上监听UDP数据报。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值