QT UDP网络编程
一、前言
在网络编程的世界中,UDP(用户数据报协议)是最简单、最快速的传输层协议之一。与TCP不同,UDP不建立连接,不保证数据到达,但正因为这样,它极快、极简单、极轻量。
想象一下现实生活中的通信方式:
TCP 像打电话:先拨号(建立连接),确保对方听到每一句话(可靠传输),最后说再见(断开连接)
UDP 像对讲机:直接说话,不确认对方是否听到,但可以同时和很多人说话(广播)
二、UDP是什么
UDP(User Datagram Protocol)是一种无连接、不可靠、基于数据报的传输层协议。
UDP核心特性:
✅ 无连接:无需建立连接,直接发送
✅ 快速:头部开销小(仅8字节),延迟极低
✅ 不可靠:不保证数据到达,不保证顺序
✅ 支持广播/组播:可以向多个目标同时发送
✅ 简单:没有流量控制、拥塞控制等复杂机制
选择UDP 选择TCP
你需要极低延迟(在线游戏、实时音视频)
可以容忍少量丢包(视频会议丢几帧不影响)
需要广播/组播功能(服务发现、实时数据分发)
处理简单查询响应(DNS查询)
有大量并发连接(IoT设备、传感器网络)
数据必须完整到达(文件传输、数据库同步)
顺序必须保证(文本传输、远程登录)
需要自动流量控制(避免网络拥塞)
使用标准协议(HTTP、FTP、SMTP等)
三、Qt UDP相关操作
3.1、UDP通信的三种方式
- 单播
// 点对点通信 udpSocket.writeDatagram(data, QHostAddress("192.168.1.100"), 8888);
- 广播
// 发给同一网络的所有主机 udpSocket.setSocketOption(QAbstractSocket::BroadcastSocketOption, 1); udpSocket.writeDatagram(data, QHostAddress::Broadcast, 8888);
- 组播
// 发给加入特定组的所有主机 QHostAddress groupAddress("224.0.0.1"); udpSocket.joinMulticastGroup(groupAddress); udpSocket.writeDatagram(data, groupAddress, 8888);
3.2、UDP通讯
#ifndef UDPCONNECT_H #define UDPCONNECT_H #include <QMainWindow> #include <QUdpSocket> //UDP发送和监听 #include <QtNetwork> #include <QList> QT_BEGIN_NAMESPACE namespace Ui { class UdpConnect; } QT_END_NAMESPACE class UdpConnect : public QMainWindow { Q_OBJECT public: UdpConnect(QWidget *parent = nullptr); ~UdpConnect(); private: Ui::UdpConnect *ui; private: void getlocalAddress();//获取本机IP地址 QUdpSocket *udpsocket; private slots: void startSevere();//开启服务 void closeServe();//关闭服务 void sendInformation();//发送消息 void broadInformation();//广播消息 void socketReadData(); //读取数据 }; #endif // UDPCONNECT_H #include "udpconnect.h" #include "ui_udpconnect.h" UdpConnect::UdpConnect(QWidget *parent) : QMainWindow(parent) , ui(new Ui::UdpConnect) { ui->setupUi(this); getlocalAddress(); udpsocket = new QUdpSocket(this); connect(udpsocket, &QUdpSocket::readyRead, this, &UdpConnect::socketReadData);//信号与槽,当udpsocket中存在数据时,自动调用 connect(ui->pushButtonStart, &QPushButton::clicked, this, &UdpConnect::startSevere); connect(ui->pushButtonClose, &QPushButton::clicked, this, &UdpConnect::closeServe); connect(ui->pushButtonSend, &QPushButton::clicked, this, &UdpConnect::sendInformation); connect(ui->pushButtonBroad, &QPushButton::clicked, this, &UdpConnect::broadInformation); } UdpConnect::~UdpConnect() { delete ui; } void UdpConnect::getlocalAddress()//获取本机所有接口IP地址 { QList<QNetworkInterface> listinterface = QNetworkInterface::allInterfaces(); for(int i = 0; i < listinterface.size(); ++i){ QNetworkInterface interface = listinterface[i]; if(interface.flags() & QNetworkInterface::IsLoopBack) continue; //跳过回环接口 QList<QNetworkAddressEntry> entryies = interface.addressEntries(); for(int i = 0; i < entryies.size(); ++i){ QHostAddress ip = entryies[i].ip(); if(ip.protocol() == QAbstractSocket::IPv4Protocol){ ui->comboBox->addItem(ip.toString()); } } } } void UdpConnect::startSevere() { qint16 port = ui->spinBoxLocalPort->value(); QString address = ui->comboBox->currentText().trimmed(); QHostAddress hostaddress(address); if(udpsocket->bind(hostaddress, port)){//绑定本机IP和地址,若成功返回True ui->textEdit->append("********** 绑定成功 **********\n"); ui->textEdit->append("********** Port:" + QString::number(udpsocket->localPort())); }else{ ui->textEdit->append("********** 绑定失败 **********\n"); } } void UdpConnect::closeServe() { udpsocket->close();//停止服务 ui->textEdit->append("********** 停止服务 **********\n"); } void UdpConnect::sendInformation() { qint16 targetport = ui->spinBoxTargetPort->value(); QString targetaddress = ui->comboBox->currentText().trimmed(); QHostAddress tagetaddress(targetaddress); QString strs = ui->lineEditSend->text().trimmed(); udpsocket->writeDatagram(strs.toUtf8(), tagetaddress, targetport);//发送数据,包含数据内容,目标IP和端口 ui->textEdit->append("你:" + strs); ui->lineEditSend->clear(); } void UdpConnect::broadInformation() { qint16 targetport = ui->spinBoxTargetPort->value(); QString strs = ui->lineEditSend->text().trimmed(); udpsocket->writeDatagram(strs.toUtf8(), QHostAddress::Broadcast, targetport);//广播通讯,发送给广播IP对应的端口 ui->textEdit->append("你(broadcast):" + strs); ui->lineEditSend->clear(); } void UdpConnect::socketReadData() { //判读是否有数据报(数据报中包含源于目标IP与端口以及发送的信息) while(udpsocket->hasPendingDatagrams()){ QNetworkDatagram datagrams = udpsocket->receiveDatagram();//数据报类型 if(datagrams.isValid()){ QByteArray data = datagrams.data();//获取数据包信息 QHostAddress address = datagrams.senderAddress();//获取数据发送发的IP和Port qint16 port = datagrams.senderPort(); ui->textEdit->append("From to:" + address.toString() + ":" + QString::number(port) + '\n'); ui->textEdit->append("对方:" + QString::fromUtf8(data) + '\n'); } } }
3.3、数据报大小限制
// UDP不保证数据到达,需要应用层处理 class ReliableUDP { void sendWithRetry(const QByteArray &data, int maxRetries = 3) { for (int i = 0; i < maxRetries; i++) { udpSocket.writeDatagram(data, target, port); if (waitForAck(1000)) { // 等待1秒确认 return; // 收到确认,发送成功 } // 超时,重试 } qDebug() << "发送失败,达到最大重试次数"; } };
四、TCP、UDP比较
UDP:面向数据报(Datagram),每次发送/接收都是一个完整的、有边界的数据包
TCP:面向字节流(Stream),数据被视为无边界的数据流,可能被拆分或合并
4.1、TCP通讯模型
- TCP服务器端:监听与接受连接
// 服务器端:QTcpServer负责监听 QTcpServer *server = new QTcpServer(this); // 1. 监听(绑定IP和端口) if (server->listen(QHostAddress::Any, 8888)) { qDebug() << "服务器开始监听端口 8888"; // 2. 连接新客户端信号 connect(server, &QTcpServer::newConnection, [=]() { // 3. 接受连接,获取通信socket QTcpSocket *clientSocket = server->nextPendingConnection(); // 4. 处理客户端数据 connect(clientSocket, &QTcpSocket::readyRead, [=]() { QByteArray data = clientSocket->readAll(); processClientData(clientSocket, data); }); // 5. 处理断开连接 connect(clientSocket, &QTcpSocket::disconnected, [=]() { clientSocket->deleteLater();//断开连接信号 }); }); }
- TCP客户端:建立连接
// 客户端:QTcpSocket负责连接 QTcpSocket *socket = new QTcpSocket(this); // 1. 连接到服务器 socket->connectToHost("192.168.1.100", 8888); // 2. 连接成功信号 connect(socket, &QTcpSocket::connected, [=]() { qDebug() << "已连接到服务器"; // 3. 发送数据 QString message = "Hello Server!"; socket->write(message.toUtf8());//转为字节 }); // 4. 接收数据 connect(socket, &QTcpSocket::readyRead, [=]() { QByteArray data = socket->readAll(); qDebug() << "收到服务器数据:" << data; }); // 5. 处理断开 connect(socket, &QTcpSocket::disconnected, [=]() { qDebug() << "与服务器断开连接"; });
4.2、UDP通讯模型
- UDP绑定与发送
// UDP:QUdpSocket负责收发数据报 QUdpSocket *udpSocket = new QUdpSocket(this); // 1. 绑定到端口(接收数据) if (udpSocket->bind(QHostAddress::Any, 8888)) { qDebug() << "UDP绑定到端口 8888"; // 2. 接收数据 connect(udpSocket, &QUdpSocket::readyRead, [=]() { while (udpSocket->hasPendingDatagrams()) { QNetworkDatagram datagram = udpSocket->receiveDatagram(); if (datagram.isValid()) { QByteArray data = datagram.data(); QHostAddress sender = datagram.senderAddress(); quint16 port = datagram.senderPort(); qDebug() << "收到来自" << sender.toString() << ":" << port; qDebug() << "数据:" << data; } } }); } // 3. 发送数据(无需连接) void sendUdpMessage(const QString &targetIp, quint16 targetPort, const QString &message) { QHostAddress target(targetIp); QByteArray data = message.toUtf8(); // 直接发送,无需建立连接 qint64 sent = udpSocket->writeDatagram(data, target, targetPort); if (sent > 0) { qDebug() << "UDP数据报发送成功"; } else { qDebug() << "发送失败:" << udpSocket->errorString(); } }
4.3、TCP流式传输 vs UDP数据报传输
- TCP流式传输示例
// 发送端:分两次发送 socket->write("Hello"); socket->write("World"); // 接收端可能的情况: // 情况1:一次收到 "HelloWorld" // 情况2:先收到 "Hel",再收到 "loWorld" // 情况3:分多次收到,每次长度不确定 // 这就是TCP的"粘包"问题:消息边界丢失
- UDP数据报传输示例
// 发送端:分两次发送 udpSocket->writeDatagram("Hello", target, port); udpSocket->writeDatagram("World", target, port); // 接收端: // 第一次接收:一定收到 "Hello"(完整数据报) // 第二次接收:一定收到 "World"(完整数据报) // 不会出现合并或拆分的情况 // UDP保持消息边界:一次发送对应一次接收

浙公网安备 33010602011771号