QT网络编程(三)——UDP协议工作原理及实战

目录

引言

一、UDP协议工作原理

1. 协议特点

2. 数据包结构

3. 传输过程

二、QUdpSocket类介绍

1. 构造函数和析构函数

2. 绑定端口

3. 发送数据报

4. 接收数据报

5. 信号和槽

6. 多播和广播

7. 设置套接字选项

8. 其他函数

三、QT中UDP通信流程

1. 创建UDP套接字

3. 发送数据

4. 接收数据

5. 处理错误和断开连接

四、实战示例

五、总结


引言

在网络编程中,UDP(User Datagram Protocol,用户数据报协议)是一种轻量级、不可靠、面向数据报的、无连接的传输层协议。由于其简单、高效的特性,UDP常被用于对实时性要求高且可以容忍少量数据丢失的应用场景,如直播、视频会议、在线游戏等。在Qt框架中,通过QUdpSocket类可以方便地实现UDP通信。本文将介绍UDP协议的工作原理,并在Qt环境下进行UDP通信的实战演示。

一、UDP协议工作原理

1. 协议特点

  • 无连接:UDP在发送数据之前不需要建立连接,因此避免了建立和维护连接所需的额外开销,从而提高了传输效率。
  • 不可靠:UDP不保证数据的可靠传输,不提供序号、确认、重传等机制来确保数据包的顺序和完整性。因此,UDP可能会出现数据包的丢失、乱序或重复。
  • 面向数据报:UDP以数据报(Datagram)为单位进行传输,每个数据报都被视为一个独立的单元,从发送方到接收方进行传输。每个数据报都有固定的最大长度(通常为64KB,包含首部)。

2. 数据包结构

UDP数据包主要包含以下几个部分:

  • 源端口号(Source Port):16位字段,用于标识发送方的应用程序所使用的端口号。
  • 目的端口号(Destination Port):16位字段,用于标识接收方的应用程序所使用的端口号。
  • 长度(Length):16位字段,指示UDP数据报的总长度,包括UDP头部和数据部分。
  • 校验和(Checksum):16位字段,用于检测数据报的完整性,以确保数据在传输过程中没有被损坏。
  • 数据(Data):可变长度部分,包含了应用程序要传输的实际数据。

3. 传输过程

  • 发送端:UDP抓取来自应用程序的数据,并尽可能快地将其发送到网络上。数据被分割成小的数据包,每个数据包都包含源端口号和目标端口号等信息。
  • 网络传输:数据包在网络上传输,可能会被路由到不同的路径,因此可能会出现数据包的丢失、乱序或重复。
  • 接收端:UDP将接收到的消息段放在队列中,应用程序每次从队列中读取一个消息段进行处理。由于UDP不保证数据的可靠传输,接收方需要自行处理可能的数据丢失或乱序问题。

二、QUdpSocket类介绍

QUdpSocket类是Qt框架中用于实现UDP通信的类,它从QAbstractSocket继承而来,因此共享了大部分接口函数。在使用之前需要在项目的.pro文件中引入网络模块:QT += network

以下是QUdpSocket类的一些核心成员函数介绍:

1. 构造函数和析构函数

  • QUdpSocket(QObject *parent = nullptr):构造函数,用于创建一个新的QUdpSocket对象。可以指定一个父对象,如果不指定,则默认为nullptr。
  • ~QUdpSocket():析构函数,用于销毁QUdpSocket对象。在对象销毁时,会自动关闭套接字并释放相关资源。

2. 绑定端口

  • bool bind(const QHostAddress &address = QHostAddress::Any, quint16 port = 0, BindMode mode = DefaultForPlatform):将QUdpSocket绑定到指定的地址和端口上。如果绑定成功,返回true;否则返回false。默认情况下,地址设置为QHostAddress::Any(监听所有地址),端口号设置为0(系统会自动选择一个可用端口)。

3. 发送数据报

  • qint64 writeDatagram(const QByteArray &datagram, const QHostAddress &host, quint16 port):向指定的主机和端口发送数据报。参数datagram是要发送的数据(QByteArray类型),host是目标主机的地址,port是目标主机的端口号。函数返回成功发送的字节数,如果发送失败则返回-1。

4. 接收数据报

  • qint64 readDatagram(char *data, qint64 maxSize, QHostAddress *address = nullptr, quint16 *port = nullptr):从套接字读取一个数据报。参数data是存储接收到的数据的缓冲区,maxSize是缓冲区的大小。如果提供了address和port参数,则函数会将发送方的地址和端口号存储在这两个参数中。函数返回成功读取的字节数,如果读取失败则返回-1。

5. 信号和槽

  • readyRead():当套接字有数据可读时,会发出此信号。通常,你需要在槽函数中连接此信号,并在槽函数中调用readDatagram()来读取数据。

6. 多播和广播

  • bool joinMulticastGroup(const QHostAddress &groupAddress, const QNetworkInterface &iface = QNetworkInterface()):将QUdpSocket加入到指定的多播组中。如果加入成功,则返回true;否则返回false。groupAddress是多播组的地址,iface是网络接口,默认为默认网络接口。
  • bool leaveMulticastGroup(const QHostAddress &groupAddress, const QNetworkInterface &iface = QNetworkInterface()):将QUdpSocket从指定的多播组中移除。如果移除成功,则返回true;否则返回false。

7. 设置套接字选项

  • bool setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value):设置套接字的选项。你可以使用此函数来设置多播TTL(生存时间)等选项。option是要设置的选项,value是选项的值。如果设置成功,则返回true;否则返回false。

8. 其他函数

  • void abort():立即关闭套接字,并丢弃写入缓冲区中的所有挂起数据。此函数将套接字重置为未连接状态。
  • bool hasPendingDatagrams() const:检查是否有待读取的数据报。如果有,则返回true;否则返回false。
  • qint64 pendingDatagramSize() const:返回第一个待读取的数据报的大小(以字节为单位)。如果没有可用的数据报,则返回-1。

以上函数是QUdpSocket类的一些核心成员函数,它们共同构成了Qt中UDP通信的基础。通过组合使用这些函数,你可以实现UDP通信的各种功能,如发送和接收数据报、处理多播和广播等。

三、QT中UDP通信流程

在Qt中,UDP通信流程主要涉及几个关键步骤,包括创建UDP套接字、绑定端口、发送和接收数据以及处理可能的错误或断开连接情况。以下是UDP通信流程的详细步骤:

1. 创建UDP套接字

首先,需要创建一个QUdpSocket对象,这是Qt中用于UDP通信的类。这个对象代表了UDP套接字,用于发送和接收数据报。

QUdpSocket *udpSocket = new QUdpSocket(this);

2. 绑定端口

接下来,需要将UDP套接字绑定到一个特定的端口上。这允许UDP套接字在该端口上监听传入的数据报。通常,可以选择绑定到任何可用的端口,或者绑定到特定的IP地址和端口组合(如果需要的话)。

udpSocket->bind(QHostAddress::Any, 12345); // 绑定到所有可用接口的12345端口

注意:如果只需要在本地通信,可以使用QHostAddress::LocalHost代替QHostAddress::Any

3. 发送数据

发送数据使用writeDatagram方法。这个方法需要指定要发送的数据(QByteArray类型)、目标IP地址(QHostAddress类型)和目标端口(quint16类型)。

QByteArray data = "Hello, UDP!";  
udpSocket->writeDatagram(data, QHostAddress::Broadcast, 12345); // 广播到所有接收者  
// 或者指定具体的IP地址  
// udpSocket->writeDatagram(data, QHostAddress("192.168.1.100"), 12345);

注意:在广播时,通常将目标IP地址设置为QHostAddress::Broadcast(即255.255.255.255),但这取决于网络配置和是否允许广播。

4. 接收数据

接收数据通过连接QUdpSocketreadyRead信号到一个槽函数来实现。当UDP套接字上有可读的数据时,readyRead信号会被触发,然后可以在槽函数中读取数据。

connect(udpSocket, &QUdpSocket::readyRead, this, &MyClass::readPendingDatagrams);  
  
void MyClass::readPendingDatagrams() {  
    while (udpSocket->hasPendingDatagrams()) {  
        QByteArray datagram;  
        datagram.resize(udpSocket->pendingDatagramSize());  
        QHostAddress sender;  
        quint16 senderPort;  
        udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);  
        // 处理接收到的数据  
        qDebug() << "Received datagram:" << datagram << "from" << sender.toString() << ":" << senderPort;  
    }  
}

5. 处理错误和断开连接

UDP是无连接的协议,因此没有真正的“断开连接”概念。但是,可以连接QUdpSocketerrorOccurreddisconnected(尽管对于UDP来说这个信号可能不常用)信号来处理错误情况。

connect(udpSocket, &QUdpSocket::errorOccurred, this, &MyClass::handleError);  
  
void MyClass::handleError(QAbstractSocket::SocketError socketError) {  
    qDebug() << "UDP socket error:" << udpSocket->errorString();  
}

四、实战示例

  • UI界面

  • 核心代码
#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QHostInfo>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QString strip = GetLocalIpAddress();
    ui->comboBox_flagIp->addItem(strip);

    udpsocket = new QUdpSocket(this);

    connect(udpsocket, SIGNAL(readyRead()), this, SLOT(SocketReadyReadData()));


}

MainWindow::~MainWindow()
{
    delete ui;
}

QString MainWindow::GetLocalIpAddress()    // 获取本地IP地址
{
    QString strhostname = QHostInfo::localHostName();
    // 根据主机名称获取IP地址
    QHostInfo hostinfo = QHostInfo::fromName(strhostname);
    QList<QHostAddress> hostaddress = hostinfo.addresses();
    QString strip = "";

    if(!hostaddress.empty())
    {
        for(int i = 0; i < hostaddress.count(); i++)
        {
            QHostAddress address = hostaddress.at(i);
            if(address.protocol() == QAbstractSocket::IPv4Protocol)
            {
                strip = address.toString();
                break;
            }
        }
    }
    return strip;
}

void MainWindow::SocketReadyReadData()     // 读取Socket传输数据信息
{
    // 读取接收到的信息
    while(udpsocket->hasPendingDatagrams()) // hasPendingDatagrams()返回真至少有一个数据报要读取
    {
        QByteArray datagrams;
        datagrams.resize(udpsocket->pendingDatagramSize());

        QHostAddress paddress;
        quint16 pport;
        // readDatagram()函数读取数据报
        udpsocket->readDatagram(datagrams.data(), datagrams.size(), &paddress, &pport);

        QString ppeer = "[From to" + paddress.toString() + ":" + QString::number(pport) + "]:";
        QString str = datagrams.data();

        ui->plainTextEdit_Dispmsg->appendPlainText(ppeer + str);
    }
}

// 启动服务
void MainWindow::on_pushButton_Start_clicked()
{
    quint16 port = ui->spinBox_Srcport->value();    // 本机UDP端口

    if(udpsocket->bind(port))   // 若成功绑定
    {
        ui->plainTextEdit_Dispmsg->appendPlainText("****启动成功****");
        ui->plainTextEdit_Dispmsg->appendPlainText("本地端口:"+QString::number(udpsocket->localPort()));

        ui->pushButton_Start->setEnabled(false);
        ui->pushButton_Stop->setEnabled(true);

    }else
    {
        ui->plainTextEdit_Dispmsg->appendPlainText("****启动失败****");
    }

}
// 停止服务
void MainWindow::on_pushButton_Stop_clicked()
{
    // 解除绑定
    udpsocket->abort();
    ui->plainTextEdit_Dispmsg->appendPlainText("****停止服务****");
    ui->pushButton_Start->setEnabled(true);
    ui->pushButton_Stop->setEnabled(false);
}
// 发送消息
void MainWindow::on_pushButton_Send_clicked()
{
    QString flagipaddress = ui->comboBox_flagIp->currentText(); // 获取目标IP地址
    QHostAddress flagaddress(flagipaddress);

    quint16 flagport = ui->spinBox_Flagport->value();   // 获取目标端口
    QString strmsg = ui->lineEdit_Inputmsg->text();     // 获取消息数据

    QByteArray str = strmsg.toUtf8();
    udpsocket->writeDatagram(str,flagaddress,flagport); // 发送数据报

    ui->plainTextEdit_Dispmsg->appendPlainText("[out]:"+strmsg);
    ui->lineEdit_Inputmsg->clear(); // 清除编辑框内容
    ui->lineEdit_Inputmsg->setFocus(); // 光标定位
}
// 广播消息
void MainWindow::on_pushButton_broadcast_clicked()
{
    quint16 flagport = ui->spinBox_Flagport->value();   // 获取目标端口
    QString strmsg = ui->lineEdit_Inputmsg->text();     // 获取消息数据
    QByteArray str = strmsg.toUtf8();
    udpsocket->writeDatagram(str,QHostAddress::Broadcast,flagport); // 发送数据报

    ui->plainTextEdit_Dispmsg->appendPlainText("[broadcast]:"+strmsg);
    ui->lineEdit_Inputmsg->clear(); // 清除编辑框内容
    ui->lineEdit_Inputmsg->setFocus(); // 光标定位
}
  • 运行结果

五、总结

Qt中的UDP通信流程包括创建UDP套接字、绑定端口、发送和接收数据以及处理错误。通过连接QUdpSocket的信号到适当的槽函数,可以方便地处理UDP通信中的各种事件。注意,由于UDP是无连接的协议,因此在设计应用时需要考虑数据的可靠性和完整性。

关于QT网络编程的文章已完结啦,其他2篇文章放在下方传送门。通过对这三篇文章的学习,我想你大概了解QT中的TCP和UDP通信啦。

传送门1:QT网络编程(一)——基础知识体系

传送门2:QT网络编程(二)——TCP协议工作原理及实战

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值