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保持消息边界:一次发送对应一次接收

     

posted @ 2026-02-13 22:31  菜鸡の编程日常  阅读(6)  评论(0)    收藏  举报