基于QT的UDP通信

简介

UDP(User Datagram Protocol,用户数据报协议)是轻量的,不可靠的、面向数据报(datagram)的、无连接的协议,它可以用于对可靠性要求不高的场景。与TCP通信不同,两个程序之间进行UDP通信不需要预先建立持久的socket连接,UDP每次发送数据报都需要指定目标地址和端口。
QUdpSocket以数据报传输数据,而不是连续的数据流。发送数据报使用函数QUdpSocket::writeDatagram(),数据报的长度一般小于512字节,每个数据报包含发送者和接收者的IP地址和端口等信息。
要进行UDP数据接收,要用QUdpSocket::bind()函数先绑定一个端口,用于接收传入的数据报,当有数据报传入时会发射readyRead()信号,使用readDatagram()函数来读取接收到的数据报。
UDP消息传送有单播、广播、组播三种模式,示意图如下:
在这里插入图片描述

  • 单播(unicast)模式:一个UDP客户端发出的数据报只发送到另一个指定地址和端口的UDP客户端,时一对一的数据传输。
  • 广播(broadcast)模式:一个UDP客户端发出的数据报,在同一网络范围内其他所有的UDP客户端都可以收到。QUdpSocket支持IPv4广播。广播经常用于实现网络发现的协议。要获取广播数据只需在数据报中指定接收端地址为QHostAddress::Broadcast,一般的广播地址为255.255.255.255。
  • 组播模式(multicast)模式:也称多播。UDP客户端加入到另一个组播IP地址指定的多播组,成员向组播地址发送的数据报组内成员都可以收到,类似于QQ群功能。QUdpSocket::joinMulticastGroup()函数实现加入多播组的功能,加入多播组后,UDP数据的收发与正常的UDP数据的收发方式一样。

使用广播和多播模式,UDP可以实现一些比较灵活的通信功能,而TCP通信只有单播模式,没有广播和多播模式。所以,UDP通信虽然不能确保数据传输的准确性,但是具有灵活性,一般的即时通信软件都是基于UDP通信的。
在单播,广播,多播模式下,UDP程序都是对等的,不像TCP通信那样分为客户端和服务器端。多播和广播的实现方式基本相同,只是数据报的目标IP地址设置不同,多播模式需要加入多播组,实现方式有较大差异。

UDP单播和广播

示例程序QUDPUnicast_Broadcast的主窗口继承自QMainWindow类,程序可以进行UDP数据报的接收和发送。
可以运行在不同的计算机上,也可以运行在不同的计算机上。在同一台计算机上运行时,两个运行实例需要绑定不同的端口,如果两个实例运行在不同的计算机上,则可以使用相同的端口,因为IP地址不同了,不会导致绑定时发生冲突。一般的UDP通信程序都是在不同的计算机上运行的,约定一个固定的端口作为通信端。

ui界面

ui界面如下:
在这里插入图片描述

程序

QUDPUnicast_Broadcast.h如下:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QLabel>
#include <QUdpSocket>
#include <QHostInfo>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    QLabel *LabSocketState;
    QUdpSocket *udpSocket;
    QStringList getLocalIP();
private slots:
    void onSocketStateChange(QAbstractSocket::SocketState socketState);
    void onSocketReadyRead();   //读取socket传入数据
    void on_act_bindPotr_triggered();
    void on_act_unbind_triggered();
    void on_pb_send_clicked();
    void on_pb_broadmsg_clicked();
    void on_act_clear_triggered();
    void on_act_exit_triggered();
};

#endif // MAINWINDOW_H

QUDPUnicast_Broadcast.cpp如下:

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    LabSocketState = new QLabel("Socket状态:");
    LabSocketState->setMinimumWidth(200);
    ui->statusBar->addWidget(LabSocketState);

    QStringList localeIp = getLocalIP();
    ui->cb_tarAddress->addItems(localeIp);

    udpSocket = new QUdpSocket(this);
    connect(udpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
    onSocketStateChange(udpSocket->state());
    connect(udpSocket,SIGNAL(readyRead()),this,SLOT(onSocketReadyRead()));
}

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

QStringList MainWindow::getLocalIP()
{
    QString hostName = QHostInfo::localHostName();
    QHostInfo hostInfo = QHostInfo::fromName(hostName);
    QStringList ipList;
    QList<QHostAddress> aHost = hostInfo.addresses();
    if(!aHost.isEmpty())
    {
        for(int i = 0; i < aHost.count(); i++)
        {
            QHostAddress aAddress = aHost.at(i);
            if(QAbstractSocket::IPv4Protocol == aAddress.protocol())
            {
                ipList.append(aAddress.toString());
            }
        }
    }
    return ipList;
}

void MainWindow::onSocketStateChange(QAbstractSocket::SocketState socketState)
{
    //socket状态变化
    switch (socketState)
    {
    case QAbstractSocket::UnconnectedState:
        LabSocketState->setText("socket状态:UnconnectedState");
        break;
    case QAbstractSocket::HostLookupState:
        LabSocketState->setText("socket状态:HostLookupState");
        break;
    case QAbstractSocket::ConnectingState:
        LabSocketState->setText("socket状态:ConnectingState");
        break;
    case QAbstractSocket::ConnectedState:
        LabSocketState->setText("socket状态:ConnectedState");
        break;
    case QAbstractSocket::BoundState:
        LabSocketState->setText("socket状态:BoundState");
        break;
    case QAbstractSocket::ClosingState:
        LabSocketState->setText("socket状态:ClosingState");
        break;
    case QAbstractSocket::ListeningState:
        LabSocketState->setText("socket状态:ListeningState");
        break;
    }
}

void MainWindow::onSocketReadyRead()
{
    //读取收到的数据
    while(udpSocket->hasPendingDatagrams())
    {
        QByteArray datagram;
        datagram.resize(udpSocket->pendingDatagramSize());
        QHostAddress peerAddr;
        quint16 peerPort;
        udpSocket->readDatagram(datagram.data(),datagram.size(),&peerAddr,&peerPort);
        QString str = datagram.data();
        QString peer = "[From "+peerAddr.toString()+":"+QString::number(peerPort)+"] ";
        ui->plainTextEdit->appendPlainText(peer+str);
    }
}

void MainWindow::on_act_bindPotr_triggered()
{
    quint16 port = ui->sb_bindPort->value();        //本机UDP端口
    if(udpSocket->bind(port))       //绑定端口成功
    {
        ui->plainTextEdit->appendPlainText("**已成功绑定");
        ui->plainTextEdit->appendPlainText("**绑定端口:"+QString::number(udpSocket->localPort()));
        ui->act_bindPotr->setEnabled(false);
        ui->act_unbind->setEnabled(true);
    }
    else
    {
        ui->plainTextEdit->appendPlainText("**绑定失败");
    }
}

void MainWindow::on_act_unbind_triggered()
{
    udpSocket->abort(); //结束绑定
    ui->act_bindPotr->setEnabled(true);
    ui->act_unbind->setEnabled(false);
    ui->plainTextEdit->appendPlainText("**已解除绑定");
}

void MainWindow::on_pb_send_clicked()
{
    QString targetIP = ui->cb_tarAddress->currentText();    //目标IP
    QHostAddress targetAddr(targetIP);
    quint16 targetPort = ui->sb_tarPort->value();
    QString msg = ui->le_msg->text();                       //发送的消息内容
    QByteArray str = msg.toUtf8();
    udpSocket->writeDatagram(str,targetAddr,targetPort);    //发出数据报
    ui->plainTextEdit->appendPlainText("[Out]:"+msg);
    ui->le_msg->clear();
    ui->le_msg->setFocus();
}

void MainWindow::on_pb_broadmsg_clicked()
{
    quint16 targetPort = ui->sb_tarPort->value();           //目标端口
    QString msg = ui->le_msg->text();                       //发送的消息内容
    QByteArray str = msg.toUtf8();
    udpSocket->writeDatagram(str,QHostAddress::Broadcast,targetPort);    //发出数据报
    ui->plainTextEdit->appendPlainText("[broadcast]:"+msg);
    ui->le_msg->clear();
    ui->le_msg->setFocus();
}

void MainWindow::on_act_clear_triggered()
{
    ui->plainTextEdit->clear();
}

void MainWindow::on_act_exit_triggered()
{
    this->close();
}

QUdpSocket类型的私有变量udpSocket是用于UDP通信的socket。
定义的槽函数,onSocketStateChange()与udpSocket的stateChange()信号相关,用于显示udpSocket当前的状态;onSocketReadyRead()槽函数与udpSocket()的readyRead()信号相关联,用于读取缓冲区的数据报。
要实现UDP数据的接收,必须先用QUdpSocket::bind()函数绑定一个端口,用于监听传入的数据报,解除绑定则使用abort()函数。
绑定端口后,socket的状态转变为已绑定状态"BoundState",解除绑定后状态变为未连接状态“UnconnectedState”。
发送点对点消息和广播消息都是用QUdpSocket::writeDatagram()函数。使用writeDatagram()函数向一个目标用户发送消息时,需要指定目标地址和端口。
在广播消息时,只需要将目标地址更换为一个特殊的地址,即广播地址QHostAddress::Broadcast,一般是255.255.255.255。
QUdpSocket发送的数据报时QByteArray类型的字节数组,数据报的长度一般不超过512字节。数据报的内容可以是文本字符串,也可以是自定义格式的二进制数据,文本字符串无需以换行符结束。
QUdpSocket接收到数据报后发射readyRead()信号,之后在关联的槽函数onSocketReadyRead()中读取缓冲区中的数据报。
其中:
hasPendingDatagrams()表示是否有待读取的传入数据报。
pendingDatagramSize()返回待读取的数据报的字节数。
readDatagram()函数用于读取数据报内容。

三、UDP组播

UDP组播是主机之间“一对一组”的通信模式,当多个客户端加入由一个组播地址定义的多组播之后,客户端向组播地址和端口发送的UDP数据报,组内成员都可以接收到,其功能类似于QQ群。
组播报文的目的地址使用D类IP地址,D类地址不能出现在IP报文的源IP地址字段,用同一个IP多播地址接收多播数据报的所有主机构成一个组,称为多播组(或组播组)。所有信息接收者都加入到一个组内,并且一旦加入之后,流向组地址的数据报立即开始向接收者传输,组中的所有成员都能接收到数据报。组中的成员是动态的,主机可以在任意时间加入和离开组。
所以,采用UDP组播必须使用一个组播地址。组播地址是D类的IP地址,有特定的地址段。多播组可以是永久的也可以是临时的。多播组地址中,有一部分是由官方分配的,称为永久多播组。永久多播组保持不变的是它的IP地址,组中的成员构成可以发生改变。永久多播组中成员的数量可以是任意的,甚至可以为零。那些没有保留下来的供永久多播组使用的IP组播地址,可以被临时多播组利用。关于组播IP地址,有如下的一些约定:

  • 224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其他地址供路由协议使用
  • 224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet
  • 224.0.2.0~238.255.255.255为用户可用组播地址(临时组地址),全网范围有效
  • 239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围有效
    所以,若是在家庭或办公室局域网内测试UDP组播功能,可以使用的组播地址范围是239.0.0.0~239.255.255.255

QUdpSocket支持UDP组播,joinMulticastGroup()函数使主机加入一个多播组,leaveMulticastGroup()函数使主机离开一个多播组,UDP组播的特点是使用组播地址,其他的端口绑定、数据报收发等功能的实现与单播UDP完全相同。

ui界面

在这里插入图片描述

程序

示例程序为QUDPMulticast.h与QUDPMulticast.cpp
QUDPMulticast.h如下:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QLabel>
#include <QUdpSocket>
#include <QHostInfo>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    QLabel *LabSocketState;
    QUdpSocket *udpSocket;
    QHostAddress groupAddress;  //组播地址
    QStringList getLocalIP();   //获取本机ip
private slots:
    void onSocketStateChange(QAbstractSocket::SocketState socketState);
    void onSocketReadyRead();
    void on_act_join_triggered();
    void on_act_unjoin_triggered();
    void on_pb_send_clicked();
};

#endif // MAINWINDOW_H

QUDPMulticast.cpp如下:

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    LabSocketState = new QLabel("Socket状态:");
    LabSocketState->setMinimumWidth(200);
    ui->statusBar->addWidget(LabSocketState);

    QStringList ipList = getLocalIP();
    ui->le_ip->setText(ipList.at(2));
    udpSocket = new QUdpSocket(this);
    udpSocket->setSocketOption(QAbstractSocket::MulticastTtlOption,1);

    connect(udpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
    onSocketStateChange(udpSocket->state());
    connect(udpSocket,SIGNAL(readyRead()),this,SLOT(onSocketReadyRead()));
}

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

QStringList MainWindow::getLocalIP()
{
    QString localName = QHostInfo::localHostName();
    QHostInfo aHost = QHostInfo::fromName(localName);
    QStringList ipList;
    QList<QHostAddress> ipAddress = aHost.addresses();
    if(!ipAddress.isEmpty())
    {
        for(int i = 0; i < ipAddress.count(); i++)
        {
            if(QAbstractSocket::IPv4Protocol == ipAddress.at(i).protocol())
            {
                ipList.append(ipAddress.at(i).toString());
            }
        }
    }
    return ipList;
}

void MainWindow::onSocketStateChange(QAbstractSocket::SocketState socketState)
{
    //socket状态变化
    switch (socketState)
    {
    case QAbstractSocket::UnconnectedState:
        LabSocketState->setText("socket状态:UnconnectedState");
        break;
    case QAbstractSocket::HostLookupState:
        LabSocketState->setText("socket状态:HostLookupState");
        break;
    case QAbstractSocket::ConnectingState:
        LabSocketState->setText("socket状态:ConnectingState");
        break;
    case QAbstractSocket::ConnectedState:
        LabSocketState->setText("socket状态:ConnectedState");
        break;
    case QAbstractSocket::BoundState:
        LabSocketState->setText("socket状态:BoundState");
        break;
    case QAbstractSocket::ClosingState:
        LabSocketState->setText("socket状态:ClosingState");
        break;
    case QAbstractSocket::ListeningState:
        LabSocketState->setText("socket状态:ListeningState");
        break;
    }
}

void MainWindow::onSocketReadyRead()
{
    //读取数据报
    while(udpSocket->hasPendingDatagrams())
    {
        QByteArray datagram;
        datagram.resize(udpSocket->pendingDatagramSize());
        QHostAddress peerAddr;
        quint16 peerPort;
        udpSocket->readDatagram(datagram.data(),datagram.size(),&peerAddr,&peerPort);
        QString str = datagram.data();
        QString peer = "[From "+peerAddr.toString()+":"+QString::number(peerPort)+"] ";
        ui->plainTextEdit->appendPlainText(peer+str);
    }
}

void MainWindow::on_act_join_triggered()
{
    //加入组播
    QString IP = ui->le_ip->text();
    groupAddress = QHostAddress(IP);    //多播组地址
    quint16 groupPort = ui->sb_port->value();
    if(udpSocket->bind(QHostAddress::AnyIPv4,groupPort,QUdpSocket::ShareAddress))
    {
        udpSocket->joinMulticastGroup(groupAddress);    //加入组播组
        ui->plainTextEdit->appendPlainText("**加入组播成功");
        ui->plainTextEdit->appendPlainText("**组播地址 IP:"+IP);
        ui->plainTextEdit->appendPlainText("**绑定端口:"+QString::number(groupPort));
        ui->act_join->setEnabled(false);
        ui->act_unjoin->setEnabled(true);
        ui->le_ip->setEnabled(false);
    }
    else
    {
        ui->plainTextEdit->appendPlainText("**加入组播组失败");
    }
}

void MainWindow::on_act_unjoin_triggered()
{
    //退出组播
    udpSocket->leaveMulticastGroup(groupAddress);   //退出组播
    udpSocket->abort(); //解除绑定
    ui->act_join->setEnabled(true);
    ui->act_unjoin->setEnabled(false);
    ui->le_ip->setEnabled(true);
    ui->plainTextEdit->appendPlainText("**已退出组播,解除端口绑定");
}

void MainWindow::on_pb_send_clicked()
{
    //发送组播消息
    quint16 groupPort = ui->sb_port->value();
    QString msg = ui->le_msg->text();
    QByteArray datagram = msg.toUtf8();
    udpSocket->writeDatagram(datagram,groupAddress,groupPort);
    ui->plainTextEdit->appendPlainText("[multicast]"+msg);
    ui->le_msg->clear();
    ui->le_msg->setFocus();
}

在.h文件中定义了一个QHostAddress类型的变量groupAddress,用于记录组播地址。
在.cpp文件中,使用QUdpSocket::setSocketQption()函数,对socket进行参数设置。

udpSocket->setSocketOption(QAbstractSocket::MulticastTtlOption,1);

将socket的QAbstractSocket::MulticastTtlOption的值设置为1,MulticastTtlOption是UDP组播的数据报的生存周期,数据报每跨过一个路由会减1。缺省为1,表示多播数据报只能在同一路由下的局域网内传播。
加入组播前,必须先绑定端口,绑定端口的语句是:

udpSocket->bind(QHostAddress::AnyIPv4,groupPort,QUdpSocket::ShareAddress)

这里指定的地址为:QHostAddress::AnyIPv4,端口为多播组统一的一个端口。
使用QUdpSocket::joinMulticastGroup();函数加入组播
多播组地址groupAddress由界面上的组合框里输入,注意,局域网内的组播地址的范围是239.0.0.0~239.255.255.255,绝不能使用本机地址作为组播地址。
退出组播使用leaveMulticastGroup()函数
加入多播组后,发送组播数据报也是使用writeDatagram()函数,只是目标地址使用的是组播地址,在readyRead()信号的槽函数里使用readDatagram()槽函数读取数据报。

参考

本文参考 Qt 5.9 C++开发指南 (王维波 栗宝鹃 侯春望) 网络编程篇,详细信息请阅读该书籍。

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Qt是一个跨平台的C++开发框架,提供了丰富的库和工具,可以用于开发各种类型的应用程序,包括UDP通信UDP通信是一种不可靠的传输协议,主要用于在网络上快速发送数据包,但不能确保数据包的可靠传输和顺序。Qt提供了QUdpSocket类,可以用于实现UDP通信。 QUdpSocket是一个用于UDP通信的套接字类。通过它,我们可以创建和管理UDP套接字,发送和接收UDP数据包。QUdpSocket提供了丰富的方法,如bind()用于绑定IP地址和端口号、writeDatagram()用于发送数据包、readyRead信号与hasPendingDatagrams()方法用于接收数据包等。 如果您想使用Qt实现UDP通信的猫源码,可以按照以下步骤进行: 1. 创建一个Qt项目,并添加QUdpSocket类的头文件。 2. 在主函数中创建QUdpSocket对象。 3. 使用bind()方法绑定本地的IP地址和端口号。 4. 使用writeDatagram()方法发送数据包。 5. 使用readyRead信号与hasPendingDatagrams()方法接收数据包,并处理接收到的数据。 在编写源码时,您可以根据需求添加其他功能,如数据包拆分和组装、数据包的校验等。此外,还可以根据实际情况处理数据丢失和顺序错乱的问题。 总之,使用Qt的QUdpSocket类可以很方便地实现UDP通信。通过自定义的猫源码,您可以实现基于UDP的猫通信程序,用于在网络上传输猫的信息。希望对您有所帮助! ### 回答2: Qt是一种跨平台的C++框架,可以用于开发图形化界面和网络通信等应用。而UDP(User Datagram Protocol)是一种无连接的传输协议,无需建立连接就可以发送数据,适用于实时性要求较高的通信场景。 Qt提供了丰富的网络编程支持,其中包括UDP通信QtUDP通信猫源码是一个示例项目,用于展示如何使用Qt框架进行UDP通信的实现。 在这个源码中,首先需要创建一个QUdpSocket对象,用于发送和接收UDP数据包。通过调用QUdpSocket的bind()函数,可以将socket绑定到指定的IP地址和端口上。 接下来,可以通过调用QUdpSocket的writeDatagram()函数来发送UDP数据包。该函数需要指定目标IP地址和端口,以及待发送的数据。如果发送成功,writeDatagram()函数将返回实际发送的数据字节数。 同时,可以通过重写QUdpSocket的readyRead()信号的槽函数,来实现数据的接收与处理。当有数据到达时,系统将自动发出readyRead()信号,可以通过连接信号与槽函数的方式,将数据接收和处理的逻辑代码写入槽函数中。 该示例项目通常会实现一个简单的UDP聊天室功能,即用户可以发送消息到指定的IP地址和端口,并接收其他用户发送的消息。通过解析接收到的数据包,可以将发送方的IP地址和消息内容进行显示。 总之,Qt UDP通信猫源码是一个展示如何使用Qt框架进行UDP通信的示例项目,可以通过该源码了解UDP通信的基本原理和代码实现。如果需要在自己的项目中使用UDP进行通信,可以参考该源码进行调整和拓展。 ### 回答3: Qt是一种跨平台的C++框架,可用于开发各种类型的应用程序。UDP(User Datagram Protocol)是一种无连接的网络传输协议,它可以在网络上发送和接收数据包,但不保证数据包的可靠传输。通信猫源码是指使用Qt开发的一种UDP通信应用程序的源代码。 UDP通信猫源码可以用于实现基于UDP协议的网络通信功能。在该源码中,通信猫可以作为服务器或客户端的角色来进行通信。作为服务器,通信猫可以监听指定的端口,接收来自客户端发送的数据包,并对数据进行处理。作为客户端,通信猫可以向指定的服务器IP地址和端口发送数据包。 通信猫源码通常包含了UDP通信相关的功能和逻辑,例如创建UDP套接字、绑定端口、发送和接收数据包等。该源码还可能包含与界面相关的代码,以便用户能够通过界面与通信猫进行交互。 使用通信猫源码,开发者可以快速实现UDP通信功能,并根据自己的需求进行定制和扩展。例如,可以基于该源码进行进一步的开发,添加数据校验、加密、压缩等功能,以提高通信的可靠性和安全性。 总之,Qt UDP通信猫源码提供了一个基于UDP协议的通信解决方案的基础代码,开发者可以在此基础上进行二次开发,以满足自己的特定需求。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值