Qt中QUdpSocket仅接收前2个数据报的问题排查与解决
Qt UDP图片传输问题:大文件拆分后仅接收前2个数据报
问题现象
在Qt环境下实现UDP协议传输图片,当图片大于50KB时拆分多个数据报发送,客户端已发送全部数据报,但服务端仅收到前2个。只有图片小于50KB或拆分后为2个数据报时,传输正常。
问题原因
- 接收逻辑的致命缺陷:服务端
slotProcessDatagrams槽函数中,每次readyRead信号触发时都会创建局部的datagram变量来临时存储数据。但UDP的readyRead信号可能分批触发——当后续数据报延迟到达时,新的信号会重新初始化空的datagram,导致之前接收的片段丢失,永远无法拼接出完整的图片。 - 缺少分片重组的必要机制:拆分的数据报没有序号、总数量等标识,服务端无法判断是否接收完整,也无法处理UDP可能出现的乱序、丢包问题。
- 数据报大小超出MTU限制:当前设置的
UDP_DATAGRAM_SIZE=50000远大于以太网MTU(1500字节,扣除IP和UDP头后有效载荷约1472字节),会被IP层强制分片,大幅提升丢包概率。
解决思路
- 修正服务端缓存逻辑:在
UDP_Server类中添加全局成员变量作为接收缓冲区,替代槽函数内的局部变量,持续累积所有收到的分片,直到确认接收完整。 - 添加自定义分片头部:每个数据报前附加头部信息,包含总分片数、当前分片序号、图片总大小等。客户端先发送头部信息,再发送分片;服务端根据序号拼接,直到收全所有分片后再解析图片。
- 增加可靠性校验机制:服务端收到每个分片后向客户端发送ACK确认,客户端未收到ACK则重发对应分片,弥补UDP的不可靠性。
- 调整分片大小:将单个数据报大小设置为不超过1472字节,避免IP层自动分片,降低丢包风险。
相关代码
main.cpp
#include <QApplication> #include "udp_server.h" #include "udp_client.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); UDP_Server server(2323); UDP_Client client(2323); server.show(); client.show(); return a.exec(); }
udp_server.h 和 udp_server.cpp
#ifndef UDP_SERVER_H #define UDP_SERVER_H #include <QWidget> #include <QUdpSocket> #include <QBoxLayout> #include <QLabel> #include <QBuffer> #include <QFile> class UDP_Server : public QWidget { Q_OBJECT public: explicit UDP_Server(int port, QWidget *parent = nullptr); private: QUdpSocket* socket; QLabel* showLabel; private slots: void slotProcessDatagrams(); }; #endif // UDP_SERVER_H //================================ #include "udp_server.h" UDP_Server::UDP_Server(int port, QWidget *parent) : QWidget{parent} { setWindowTitle("SERVER"); socket = new QUdpSocket(this); socket->bind(QHostAddress::LocalHost, port); connect(socket, SIGNAL(readyRead()), SLOT(slotProcessDatagrams())); showLabel = new QLabel; QVBoxLayout* box = new QVBoxLayout; box->addWidget(showLabel); setLayout(box); } void UDP_Server::slotProcessDatagrams() { QByteArray datagram; QPixmap frame; while (socket->hasPendingDatagrams()) { QByteArray receivedData; receivedData.resize(socket->pendingDatagramSize()); socket->readDatagram(receivedData.data(), receivedData.size()); datagram.append(receivedData); qDebug() << "read: " << receivedData.size(); } if (frame.loadFromData(datagram, "PNG")) { showLabel->setPixmap(frame); } else { qDebug() << "Failed to read frame"; } }
udp_client.h 和 udp_client.cpp
#ifndef UDP_CLIENT_H #define UDP_CLIENT_H #include <QWidget> #include <QUdpSocket> #include <QPushButton> #include <QBoxLayout> #include <QTextEdit> #include <QBuffer> #include <QFile> class UDP_Client : public QWidget { Q_OBJECT public: explicit UDP_Client(int port, QWidget *parent = nullptr); private: QUdpSocket* socket; int port; private slots: void slotSendDatagram(); }; #endif // UDP_CLIENT_H //================ #include "udp_client.h" UDP_Client::UDP_Client(int port, QWidget *parent) : QWidget{parent}, port(port) { setWindowTitle("CLIENT"); socket = new QUdpSocket(this); QPushButton* btn = new QPushButton("Send"); connect(btn, SIGNAL(clicked()), SLOT(slotSendDatagram())); QVBoxLayout* box = new QVBoxLayout; box->addWidget(btn); setLayout(box); } const int UDP_DATAGRAM_SIZE = 50000; void UDP_Client::slotSendDatagram() { QFile file("path/to/image"); if (file.open(QIODevice::ReadOnly)) { while (!file.atEnd()) { QByteArray datagram = file.read(UDP_DATAGRAM_SIZE); socket->writeDatagram(datagram, QHostAddress::LocalHost, port); qDebug() << "send: " << datagram.size(); } file.close(); } }
调试输出
send: 50000 send: 50000 send: 50000 send: 24753 read: 50000 read: 50000 Failed to read frame
内容的提问来源于stack exchange,提问作者TuleKr1s
相关产品推荐
相关产品推荐

