Qt —UDP通信QUdpSocket 简介 +案例

news2024/11/18 6:03:56

1. UDP通信概述


   UDP是无连接、不可靠、面向数据报(datagram)的协议,可以应用于对可靠性要求不高的场合。与TCP通信不同,UDP通信无需预先建立持久的socket连接,UDP每次发送数据报都需要指定目标地址和端口。

   QUdpSocket以数据报传输数据,而不是以连续的数据流。发送数据报使用函数         QUdpSocket::writeDatagram()数据报的长度一般少于512字节,每个数据报包含发送者和接收者的IP地址和端口等信息。
     UDP数据接收,首先要使用QUdpSocket::bind()绑定一个端口,绑定端口后,socket的状态会变为已绑定状态“BoundState”。

当有数据报传入时,QudpSocket会自动发射readyRead()信号,在其槽函数中使用QUdpSocket::readDatagram()进行数据读取。abort()为解除绑定,解除后socket状态变为未连接状态“UnconnectedState”。

2. UDP消息传送的三种模式

单播模式(unicast):一个UDP客户端发送数据报到指定地址和端口的另一UDP客户端,是一对一的数据传输。

广播模式(broadcast):一个UDP客户端发出的数据报,在同一网络范围内其他所有的UDP客户端都可以收到。QUdpSocket支持IPv4广播。需要在数据报中指定接收端地址为QHostAddress::Broadcast,一般的广播地址是255.255.255.255

组播模式(multicast):UDP客户端加入到另一个组播IP地址的多播组,成员向组播地址发送的数据报,其加入组播的所有成员都可以接收到,类似于QQ群功能。QUdpSocket::joinMulticastGroup()函数实现加入多播组的功能。

QUdpSocket::leaveMulticastGroup()函数实现

在单播、广播和多播模式下,UDP程序都是对等的,不像TCP通信分为客户端和服务端。
TCP通信只有单播模式。UDP通信虽然不能保证数据传输的准确性,但是具有灵活性,一般的即时通信软件都是基于UDP通信的。

3. QUdpSocket类的接口函数

bool bind(quint16 port = 0)  为UDP通信绑定一个端口

qint64 writeDatagram(QByteArray& datagram, QHostAddress& host, quint16 port) 向目标地址和端口的UDP客户端发送数据报,返回成功发送的字节数,数据报的长度一般不超过512字节。

bool hasPendingDatagrams()     当至少有一个数据报需要读取时,返回true

qint64 pendingDatagramSize()    返回第一个待读取的数据报的大小

qint64 readDatagram(char* data, qint64 maxSize) 读取一个数据报,返回成功读取的字节数

qint64 readDatagram(char* data, qint64 maxSize, QHostAddress* address, quint16* port) 读取一个数据报,返回成功读取的字节数。发送方的主机地址和端口存储在*address和*port中(除非指针为0)

bool joinMulticastGroup(QHostAddress& groupAddress)      加入一个多播组

bool leaveMulticastGroup(QHostAddress& groupAddress)    离开一个多播组

void abort() 终止当前连接并重置套接字。通常在析构函数中写入。与disconnectFromHost()不同,该函数立即关闭套接字,丢弃写入缓冲区中的任何挂起数据。
原文链接:https://blog.csdn.net/WL0616/article/details/129050373

4.UDP对话小案例

实现发送和接收端互相发信息,类似QQ  (界面使用UI设计)

4.1.接收端

receiver.h

#ifndef RECESIVER_H
#define RECESIVER_H

#include <QWidget>
#include <QUdpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Recesiver; }
QT_END_NAMESPACE

class Recesiver : public QWidget
{
    Q_OBJECT

public:
    Recesiver(QWidget *parent = nullptr);
    ~Recesiver();

private slots:
    void on_pushButton_2_clicked(); //启动槽函数
    void start();
    void on_pushButton_clicked();

private:
    Ui::Recesiver *ui;
    QUdpSocket *receiver;
};
#endif // RECESIVER_H

receiver.cpp

#include "recesiver.h"
#include "ui_recesiver.h"

Recesiver::Recesiver(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Recesiver)
{
    ui->setupUi(this);
    setWindowTitle(QStringLiteral("接收端"));
    ui->lineEdit->setText("127.0.0.1");
    receiver =new QUdpSocket(this);
}

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

//接收信息
 void Recesiver::start()
 {
    QByteArray datagram;
    datagram.resize(receiver->pendingDatagramSize()); //接收到的数据的长度
    receiver->readDatagram(datagram.data(),datagram.size());
    ui->textEdit->append(QStringLiteral("对方:")+datagram);
 }

//启动按钮(发送端发送信息给接收端)
void Recesiver::on_pushButton_2_clicked()
{
   receiver->bind(ui->lineEdit_2->text().toInt());//设置端口号将其转为整型
   connect(receiver,&QUdpSocket::readyRead,this,[&](){start();});
   ui->pushButton_2->setEnabled(false);
   ui->lineEdit_2->setEnabled(false);
}

//发送按钮(接收端发送信息给发送端)
void Recesiver::on_pushButton_clicked()
{
    QByteArray datagram=ui->textEdit_2->toPlainText().toUtf8();  //在输入端输入发送的内容
    receiver->writeDatagram(datagram.data(),datagram.size(),QHostAddress(ui->lineEdit->text()),ui->lineEdit_3->text().toInt());
    //qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &host, quint16 port);
    //发送数据,大小,发送主机的ip,发送主机的端口号
    ui->textEdit->append(QStringLiteral("本机:")+ui->textEdit_2->toPlainText());//发送信息的具体内容在发送端的聊天记录里能体现
    ui->textEdit_2->clear();
}

4.2发送端

sender.h

#ifndef SENDER_H
#define SENDER_H
#include <QWidget>
#include <QUdpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Sender; }
QT_END_NAMESPACE

class Sender : public QWidget
{
    Q_OBJECT

public:
    Sender(QWidget *parent = nullptr);
    ~Sender();
    void  start2();
private slots:
    void on_pushButton_clicked();

private:
    Ui::Sender *ui;
    QUdpSocket *sender;
};
#endif // SENDER_H

sender.cpp

#include "sender.h"
#include "ui_sender.h"

Sender::Sender(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Sender)
{
    ui->setupUi(this);
    sender=new QUdpSocket(this);
    setWindowTitle(QStringLiteral("发送端"));

    //sender
    ui->lineEdit_3->setText("888");
    sender->bind(ui->lineEdit_3->text().toInt());//绑定端口号

    connect(sender,&QUdpSocket::readyRead,this,[&](){start2();});
    //start2();
}

void Sender::start2()
{
    QByteArray datagram;
    datagram.resize(sender->pendingDatagramSize()); //接收到的数据的长度
    sender->readDatagram(datagram.data(),datagram.size());
    ui->textEdit->append(QStringLiteral("对方:")+datagram);
}


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


//发送按钮 发送信息给接收端
void Sender::on_pushButton_clicked()
{
   QByteArray datagram=ui->textEdit_2->toPlainText().toUtf8();  //在输入端输入发送的内容
   sender->writeDatagram(datagram.data(),datagram.size(),QHostAddress(ui->lineEdit->text()),ui->lineEdit_2->text().toInt());
   //qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &host, quint16 port);
   //发送数据,大小,发送主机的ip,发送主机的端口号
   ui->textEdit->append(QStringLiteral("本机:")+ui->textEdit_2->toPlainText());//发送信息的具体内容在发送端的聊天记录里能体现
   ui->textEdit_2->clear();

}

结果:

(ps:对话有点搞笑,哈哈哈)

5.4. UDP单播和广播代码示例

4.1 测试说明

  本实例实现UDP通信的单播和广播。两个实例可以运行在同一台计算机上,也可以运行在不同的计算机上。
这里的两个实例是运行在同一台计算机上,需要注意,在同一台计算机上运行时,两个实例需要绑定不同的端口。例如实例A绑定端口1600,实例B绑定端口3200,实例A向实例B发送数据报时,需要指定实例B的端口,这样实例B才能收到数据。
如果两个实例在不同的计算机上运行,则端口可以一样,因为IP地址不同了,不会导致绑定时发生冲突。一般的UDP通信程序都是在不同的计算机上运行的,约定一个固定的端口作为通信端口。

4.2主要程序

用UI设计器件和代码化UI分别设计

ui设计器设计:

.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QLabel>
#include <QAbstractSocket>
#include <QUdpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    void   handleEvents();                                                 // 信号与槽处理

private:
    Ui::MainWindow *ui;

    QUdpSocket *udpScoket;
    QLabel *labstateScoket;   //Scoket状态栏标签
    QString  getLocalIp();  //获取本机的IP地址
private slots:

    void slotActBindPort();   //绑定端口
    void slotActUnbindPort(); //解除端口
    void slotActClearText();  //清空文本框
    void slotActQuit();       //退出

    void slotSocketReadyRead();  //读取socket传入的数据
    void slotSocketStateChanged(QAbstractSocket::SocketState socketState);

    void on_pushButton_clicked(); //发送信息
    void on_pushButton_2_clicked(); //广播信息
};
#endif // MAINWINDOW_H

.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QTextEdit>
#include <QMessageBox>
#include <QHostInfo>

#include <QAction>
#include <QPushButton>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QString localIP = getLocalIp(); //获取本机的IP地址
    this->setWindowTitle(this->windowTitle() + "---IP:" + localIP);

    ui->comboBox->addItem(localIP); //在目标地址中添加IP
    udpScoket = new QUdpSocket(this);

    //状态栏
    labstateScoket = new QLabel(QStringLiteral("socket状态:"), this);
    labstateScoket->setMinimumWidth(150);
    ui->statusBar->addWidget(labstateScoket);
    handleEvents();

    ui->spinBox->setMinimum(1);     //设置绑定端口的最大最小值和当前的端口值
    ui->spinBox->setMaximum(65535);
    ui->spinBox->setValue(1600);

    ui->spinBox_2->setMinimum(1);   //设置目标端口的最大最小值和当前的端口值
    ui->spinBox_2->setMaximum(65535);
    ui->spinBox_2->setValue(3200);

}

MainWindow::~MainWindow()
{
    udpScoket->abort();
    delete udpScoket;
    udpScoket = nullptr;
    delete ui;
}


// 信号与槽处理
void MainWindow::handleEvents()
{

    connect(ui->action, &QAction::triggered, this, &MainWindow::slotActBindPort );
    connect(ui->action_2, &QAction::triggered, this, &MainWindow::slotActUnbindPort);
    connect(ui->action_3, &QAction::triggered, this, &MainWindow::slotActClearText);
    connect(ui->action_4, &QAction::triggered, this, &MainWindow::slotActQuit);

    connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::on_pushButton_clicked);
    connect(ui->pushButton_2, &QPushButton::clicked, this, &MainWindow::on_pushButton_2_clicked);

    connect(udpScoket, &QUdpSocket::stateChanged, this, &MainWindow::slotSocketStateChanged);
    connect(udpScoket, &QUdpSocket::readyRead, this, &MainWindow::slotSocketReadyRead);

}


QString MainWindow::getLocalIp()
{
    QString hostName = QHostInfo::localHostName();//获取本机主机名
    QHostInfo hostInfo = QHostInfo::fromName(hostName);  //返回主机名的IP地址

    QString localIP = "";
    QList<QHostAddress> addrList = hostInfo.addresses();   //主机IP地址列表

    if (!addrList.isEmpty())
    {
        for (int i = 0; i < addrList.size(); i++)
        {
            QHostAddress addr = addrList.at(i);
            if (QAbstractSocket::IPv4Protocol == addr.protocol())
            {
                localIP = addr.toString();
                break;
            }
        }
    }
    return localIP;
}


//绑定端口
void MainWindow::slotActBindPort()
{
    quint16 port=ui->spinBox->value();   //绑定本机UDp端口
    if(udpScoket->bind(port))
    {
        ui->textEdit->append(QStringLiteral("**已经绑定成功"));
        ui->textEdit->append(QStringLiteral("**绑定端口:")+QString::number(udpScoket->localPort()));
        ui->action->setEnabled(false); //开始绑定失效
        ui->action_2->setEnabled(true);//解除绑定使能
    }
    else
        ui->textEdit->append(QStringLiteral("绑定失败"));
}

//解除端口
void MainWindow::slotActUnbindPort()
{

    udpScoket->abort();//解除绑定
    ui->action->setEnabled(true); //开始绑定失效
    ui->action_2->setEnabled(false);//解除绑定使能
    ui->textEdit->append(QStringLiteral("**已经解除绑定"));

}

//清空文本框
void MainWindow::slotActClearText()
{
    ui->textEdit->clear();
}


//退出
void MainWindow::slotActQuit()
{
    QMessageBox::StandardButton button = QMessageBox::question(this, "", QStringLiteral("是否要退出?"));
    if (button == QMessageBox::StandardButton::Yes)
        this->close();
}

//读取socket传入的数据
void MainWindow::slotSocketReadyRead()
{
    while(udpScoket->hasPendingDatagrams()) //当有数据传入数据报
     {
       QByteArray  datagtam;
       datagtam.resize(udpScoket->pendingDatagramSize());//读取数据报大小

       QHostAddress peerAddr;  //在已连接的状态下,返回对方的socket的地址
       quint16 peerPort;       //在已连接的状态下,返回对方的socket的端口

       udpScoket->readDatagram(datagtam.data(),datagtam.size(),&peerAddr,&peerPort);//读取数据报的内容
       //qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *host = nullptr, quint16 *port = nullptr);

       QString str=datagtam.data();
       QString  peer="[From "+peerAddr.toString()+":"+QString::number(peerPort)+"]";
       ui->textEdit->append( peer + str);
     }
}

 //状态栏显示Socket变化情况
void MainWindow::slotSocketStateChanged(QAbstractSocket::SocketState socketState)
{
    switch (socketState)
    {
            case QAbstractSocket::UnconnectedState: labstateScoket->setText(QStringLiteral("socket状态:UnconnectedState")); break;
            case QAbstractSocket::HostLookupState: labstateScoket->setText(QStringLiteral("socket状态:HostLookupState")); break;
            case QAbstractSocket::ConnectingState: labstateScoket->setText(QStringLiteral("socket状态:ConnectingState")); break;
            case QAbstractSocket::ConnectedState: labstateScoket->setText(QStringLiteral("socket状态:ConnectedState")); break;
            case QAbstractSocket::BoundState: labstateScoket->setText(QStringLiteral("socket状态:BoundState")); break;
            case QAbstractSocket::ClosingState: labstateScoket->setText(QStringLiteral("socket状态:ClosingState")); break;
            default: break;
     }

}


//发送信息
void MainWindow::on_pushButton_clicked()
{
        QString msg = ui->lineEdit->text(); //发送信息
        QByteArray str = msg.toUtf8();

        QString targetIp = ui->comboBox->currentText();  //目标IP就是主机地址的ip
        QHostAddress targetAddr(targetIp);
        quint16 targetPort = ui->spinBox_2->value();  //目标端口

        udpScoket->writeDatagram(str, targetAddr, targetPort);//发出数据报
        ui->textEdit->append("[out] " + msg);
        ui->lineEdit->clear();
        ui->lineEdit->setFocus();

}
//广播信息
void MainWindow::on_pushButton_2_clicked()
{
      quint16 targetPort = ui->spinBox_2->value();  //目标端口
      QString msg=ui->lineEdit->text();

      QByteArray str = msg.toUtf8();
      udpScoket->writeDatagram(str, QHostAddress::Broadcast, targetPort);//发出数据报
      ui->textEdit->append("[broadcast] " + msg);
      ui->lineEdit->clear();
      ui->lineEdit->setFocus();

}

代码化:

.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QAction>
#include <QComboBox>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QHostInfo>
#include <QLabel>
#include <QLineEdit>
#include <QMainWindow>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QSpinBox>
#include <QUdpSocket>
#include <QVBoxLayout>

namespace Ui {
    class MainWindow;
}

class MainWindow : public QMainWindow {
    Q_OBJECT

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

private slots:
    void slotActBindPort();
    void slotActUnbindPort();
    void slotActClearText();
    void slotActQuit();
    void slotSocketStateChanged(QAbstractSocket::SocketState socketState);
    void slotBtnSend();
    void slotBtnBroad();
    void slotSocketReadyRead();  //读取socket传入的数据

private:
    Ui::MainWindow* ui;

    QAction* m_pActBindPort;
    QAction* m_pActUnbindPort;
    QAction* m_pActClearText;
    QAction* m_pActQuit;
    QWidget* m_pCentralWidget;
    QLabel* m_pLabBindPort;
    QLabel* m_PLabTargetAddr;
    QLabel* m_pLabTargetPort;
    QSpinBox* m_pSpinBindPort;
    QComboBox* m_pComboTargetAddr;
    QSpinBox* m_pSpinTargetPort;
    QLineEdit* m_pLineEdit;
    QPushButton* m_pBtnSend;
    QPushButton* m_pBtnBroad;
    QPlainTextEdit* m_pPlainText;
    QLabel* m_pLabState;
    QUdpSocket* m_pUdpSocket;

    QString getLocalIP();
};

#endif  // MAINWINDOW_H

.cpp

#include "mainwindow.h"
#include <QToolBar>
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
    ui->setupUi(this);
    //this->setWindowIcon(QIcon(":/new/prefix1/res/TitleIcon.png"));
    this->setWindowTitle(QStringLiteral("UDP Send/Receiver"));

    //工具栏
    ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
    m_pActBindPort = new QAction(QIcon(":/new/Check.png"), QStringLiteral("绑定端口"), this);
    m_pActUnbindPort = new QAction(QIcon(":/new/Break.png"), QStringLiteral("结束绑定"), this);
    m_pActClearText = new QAction(QIcon(":/new/remove.png"), QStringLiteral("清空文本"), this);
    m_pActQuit = new QAction(QIcon(":/new/back.png"), QStringLiteral("退出"), this);
    ui->toolBar->addAction(m_pActBindPort);
    ui->toolBar->addAction(m_pActUnbindPort);
    ui->toolBar->addSeparator();
    ui->toolBar->addAction(m_pActClearText);
    ui->toolBar->addSeparator();
    ui->toolBar->addAction(m_pActQuit);

    //界面布局
    m_pCentralWidget = new QWidget(this);

    QHBoxLayout* HLay1 = new QHBoxLayout;
    m_pLabBindPort = new QLabel(QStringLiteral("绑定端口"), m_pCentralWidget);
    m_pSpinBindPort = new QSpinBox(m_pCentralWidget);
    m_pSpinBindPort->setMinimum(1);
    m_pSpinBindPort->setMaximum(65535);
    m_pSpinBindPort->setValue(3200);
    m_PLabTargetAddr = new QLabel(QStringLiteral("目标地址"), m_pCentralWidget);
    m_pComboTargetAddr = new QComboBox(m_pCentralWidget);
    m_pLabTargetPort = new QLabel(QStringLiteral("目标端口"), m_pCentralWidget);
    m_pSpinTargetPort = new QSpinBox(m_pCentralWidget);
    m_pSpinTargetPort->setMinimum(1);
    m_pSpinTargetPort->setMaximum(65535);
    m_pSpinTargetPort->setValue(1600);
    HLay1->addWidget(m_pLabBindPort, 1, Qt::AlignRight);
    HLay1->addWidget(m_pSpinBindPort, 2);
    HLay1->addWidget(m_PLabTargetAddr, 1, Qt::AlignRight);
    HLay1->addWidget(m_pComboTargetAddr, 4);
    HLay1->addWidget(m_pLabTargetPort, 1, Qt::AlignRight);
    HLay1->addWidget(m_pSpinTargetPort, 2);

    QHBoxLayout* HLay2 = new QHBoxLayout;
    m_pLineEdit = new QLineEdit(m_pCentralWidget);
    m_pBtnSend = new QPushButton(QStringLiteral("发送消息"), m_pCentralWidget);
    m_pBtnBroad = new QPushButton(QStringLiteral("广播消息"), m_pCentralWidget);
    HLay2->addWidget(m_pLineEdit);
    HLay2->addWidget(m_pBtnSend);
    HLay2->addWidget(m_pBtnBroad);

    QVBoxLayout* VLay = new QVBoxLayout(m_pCentralWidget);  //主布局必须设置parent,否则不会显示布局
    // QVBoxLayout* VLay = new QVBoxLayout();
    VLay->addLayout(HLay1);
    VLay->addLayout(HLay2);
    m_pPlainText = new QPlainTextEdit(m_pCentralWidget);
    VLay->addWidget(m_pPlainText);

    this->setCentralWidget(m_pCentralWidget);
    this->setLayout(VLay);  //设置为窗体的主布。在指定了主布局的parent之后,这句话可有可无

    QString localIP = getLocalIP();
    this->setWindowTitle(this->windowTitle() + "---IP:" + localIP);
    m_pComboTargetAddr->addItem(localIP);
    m_pUdpSocket = new QUdpSocket(this);

    //状态栏
    m_pLabState = new QLabel(QStringLiteral("socket状态:"), this);
    m_pLabState->setMinimumWidth(150);
    ui->statusbar->addWidget(m_pLabState);

    // connect
    connect(m_pActBindPort, &QAction::triggered, this, &MainWindow::slotActBindPort);
    connect(m_pActUnbindPort, &QAction::triggered, this, &MainWindow::slotActUnbindPort);
    connect(m_pActClearText, &QAction::triggered, this, &MainWindow::slotActClearText);
    connect(m_pActQuit, &QAction::triggered, this, &MainWindow::slotActQuit);
    connect(m_pBtnSend, &QPushButton::clicked, this, &MainWindow::slotBtnSend);
    connect(m_pBtnBroad, &QPushButton::clicked, this, &MainWindow::slotBtnBroad);
    connect(m_pUdpSocket, &QUdpSocket::stateChanged, this, &MainWindow::slotSocketStateChanged);
    connect(m_pUdpSocket, &QUdpSocket::readyRead, this, &MainWindow::slotSocketReadyRead);
}

MainWindow::~MainWindow() {
    //m_pUdpSocket->abort();
    //delete m_pUdpSocket;
    //m_pUdpSocket = nullptr;
    delete ui;
}

void MainWindow::slotActBindPort()
{
    quint16 port = m_pSpinBindPort->value();  //本机UDP端口
    if (m_pUdpSocket->bind(port)) {
        m_pPlainText->appendPlainText(QStringLiteral("**已成功绑定"));
        m_pPlainText->appendPlainText(QStringLiteral("绑定端口:") + QString::number(m_pUdpSocket->localPort()));

        //使能
        m_pActBindPort->setEnabled(false);
        m_pActUnbindPort->setEnabled(true);
    } else {
        m_pPlainText->appendPlainText(QStringLiteral("绑定失败"));
    }
}

void MainWindow::slotActUnbindPort() {
    m_pUdpSocket->abort();  //解除绑定
    m_pPlainText->appendPlainText(QStringLiteral("**已解除绑定"));

    m_pActBindPort->setEnabled(true);
    m_pActUnbindPort->setEnabled(false);
}

void MainWindow::slotActClearText() { m_pPlainText->clear(); }

void MainWindow::slotActQuit() {
    QMessageBox::StandardButton button = QMessageBox::question(this, "", QStringLiteral("是否要退出?"));
    if (button == QMessageBox::StandardButton::Yes)
        this->close();
}

void MainWindow::slotSocketStateChanged(QAbstractSocket::SocketState socketState) {
    switch (socketState)
    {
            case QAbstractSocket::UnconnectedState: m_pLabState->setText(QStringLiteral("socket状态:UnconnectedState")); break;
            case QAbstractSocket::HostLookupState: m_pLabState->setText(QStringLiteral("socket状态:HostLookupState")); break;
            case QAbstractSocket::ConnectingState: m_pLabState->setText(QStringLiteral("socket状态:ConnectingState")); break;
            case QAbstractSocket::ConnectedState: m_pLabState->setText(QStringLiteral("socket状态:ConnectedState")); break;
            case QAbstractSocket::BoundState: m_pLabState->setText(QStringLiteral("socket状态:BoundState")); break;
            case QAbstractSocket::ClosingState: m_pLabState->setText(QStringLiteral("socket状态:ClosingState")); break;
            default: break;
     }

}

void MainWindow::slotBtnSend()
{
    QString msg = m_pLineEdit->text();
    QByteArray str = msg.toUtf8();
    QString targetIp = m_pComboTargetAddr->currentText();  //目标IP
    QHostAddress targetAddr(targetIp);
    quint16 targetPort = m_pSpinTargetPort->value();  //目标端口
    m_pUdpSocket->writeDatagram(str, targetAddr, targetPort);
    m_pPlainText->appendPlainText("[out] " + msg);
    m_pLineEdit->clear();
    m_pLineEdit->setFocus();

}

void MainWindow::slotBtnBroad()
{
    QString msg = m_pLineEdit->text();
    QByteArray str = msg.toUtf8();
    quint16 targetPort = m_pSpinTargetPort->value();
    m_pUdpSocket->writeDatagram(str, QHostAddress::Broadcast, targetPort);
    m_pPlainText->appendPlainText("[out] " + msg);
    m_pLineEdit->clear();
    m_pLineEdit->setFocus();



}

void MainWindow::slotSocketReadyRead()
{
    while (m_pUdpSocket->hasPendingDatagrams())
    {
        QByteArray dataGram;
        dataGram.resize(m_pUdpSocket->pendingDatagramSize());
        QHostAddress peerAddress;
        quint16 peerPort;
        m_pUdpSocket->readDatagram(dataGram.data(), dataGram.size(), &peerAddress, &peerPort);
        QString str = dataGram.data();
        QString peer = "[From " + peerAddress.toString() + ":" + QString::number(peerPort) + "]";
        m_pPlainText->appendPlainText(peer + str);
    }
}

QString MainWindow::getLocalIP() {
    QString hostName = QHostInfo::localHostName();
    QHostInfo hostInfo = QHostInfo::fromName(hostName);
    QString localIP = "";
    QList<QHostAddress> addrList = hostInfo.addresses();
    if (!addrList.isEmpty()) {
        for (int i = 0; i < addrList.size(); i++) {
            QHostAddress addr = addrList.at(i);
            if (QAbstractSocket::IPv4Protocol == addr.protocol()) {
                localIP = addr.toString();
                break;
            }
        }
    }
    return localIP;
}

结果: 

 发现自己用UI设计时,发送信息总会出现两个发送信号,并且有一个是空白的,暂时还没有找到问题所在之处,嘤嘤嘤~(/≧▽≦)/

5. UDP组播代码示例

5.1 组播的特性

组播报文的目的地址使用D类IP地址,D类地址不能出现在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组播功能,可以使用这些IP。
原文链接:https://blog.csdn.net/WL0616/article/details/129050373

常量定义

描述

QUdpSocket::ShareAddress

0x1

1、允许其他服务绑定同样的地址和端口
2、当多进程通过监听同一地址和端口,进而共享单个服务的负载时,将十分有用(例如:一个拥有几个预先建立的监听者的WEB服务器能够改善响应时间)。不过,由于任何服务都允许重新绑定(rebind),该选项应该引起某些安全上的考虑
3、需要注意的是,把该选项和ReuseAddressHint结合,也会允许你的服务重新绑定一个已存在的共享地址
4、在Unix上,该选项等同于SO_REUSEADDR;在Windows上,该选项被忽略

QUdpSocket::DontShareAddress

0x2

1、采用专有的方式绑定某个地址和端口,其他任何服务都不能再重新绑定

2、通过该选项,确保绑定成功,指定的服务将是地址和端口唯一监听者,就算是拥有ReuseAddressHint的服务也不允许重新绑定

3、在安全性上,该选项优于ShareAddress,但是在某些操作系统上需要管理员的权限才能运行

4、在Unix和Mac OS上,绑定地址和端口的默认行为是非共享,所以该选项会被忽略;在Windows上,等同于SO_EXCLUSIVEADDRUSE套接字选项

QUdpSocket::ReuseAddressHint

0x4

1、为QUdpSocke提供提示,即在地址和端口已经被其他套接字绑定的情况下,也应该试着重新绑定

2、在Unix上,该选项被忽略;在Windows上等同于SO_REUSEADDR 套接字选项

QUdpSocket::DefaultForPlatform

0x0

1、当前平台的默认选项

2、在Unix和Mac OS上,该选项等同于DontShareAddress + ReuseAddressHint;在Windows上等同于ShareAddress

5.2主要程序 

.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QAction>
#include <QComboBox>
#include <QHBoxLayout>
#include <QHostInfo>
#include <QLabel>
#include <QLineEdit>
#include <QMainWindow>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QRegExp>
#include <QSpinBox>
#include <QUdpSocket>
#include <QVBoxLayout>

namespace Ui {
    class MainWindow;
}

class MainWindow : public QMainWindow {
    Q_OBJECT

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

private slots:
    void slotActJoinMulti();
    void slotActLeaveMulti();
    void slotActClearText();
    void slotActQuit();
    void slotSocketStateChanged(QAbstractSocket::SocketState socketState);
    void slotBtnMultiMsg();
    void slotReadyRead();

private:
    Ui::MainWindow* ui;

    QAction* m_pActJoinMulti;
    QAction* m_pActLeaveMulti;
    QAction* m_pActClearText;
    QAction* m_pActQuit;
    QWidget* m_pCentralWidget;
    QLabel* m_pLabPort;
    QLabel* m_pLabAddr;
    QSpinBox* m_pSpinPort;
    QComboBox* m_pComboAddr;
    QLineEdit* m_pLineEdit;
    QPushButton* m_pBtnSendMulti;
    QPlainTextEdit* m_pPlainText;
    QLabel* m_pLabState;
    QUdpSocket* m_pUdpSocket;
    QHostAddress m_multicastAddr;

    QString getLocalIP();
};

#endif  // MAINWINDOW_H

.cpp

#include "mainwindow.h"

#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
    ui->setupUi(this);
    this->setWindowTitle(QStringLiteral("UDP Multicast"));

    //工具栏
    ui->mainToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
    m_pActJoinMulti = new QAction(QIcon(":/new/prefix1/res/添加群组.png"), QStringLiteral("加入组播"), this);
    m_pActLeaveMulti = new QAction(QIcon(":/new/prefix1/res/退出群组.png"), QStringLiteral("退出组播"), this);
    m_pActClearText = new QAction(QIcon(":/new/prefix1/res/清空.png"), QStringLiteral("清空文本"), this);
    m_pActQuit = new QAction(QIcon(":/new/prefix1/res/退出.png"), QStringLiteral("退出"), this);
    ui->mainToolBar->addAction(m_pActJoinMulti);
    ui->mainToolBar->addAction(m_pActLeaveMulti);
    ui->mainToolBar->addSeparator();
    ui->mainToolBar->addAction(m_pActClearText);
    ui->mainToolBar->addSeparator();
    ui->mainToolBar->addAction(m_pActQuit);

    //界面布局
    m_pCentralWidget = new QWidget(this);
    m_pLabPort = new QLabel(QStringLiteral("组播端口"), m_pCentralWidget);
    m_pSpinPort = new QSpinBox(m_pCentralWidget);
    m_pSpinPort->setMinimum(1);
    m_pSpinPort->setMaximum(65535);
    m_pSpinPort->setValue(3200);
    m_pLabAddr = new QLabel(QStringLiteral("组播地址"), m_pCentralWidget);
    m_pComboAddr = new QComboBox(m_pCentralWidget);
    m_pComboAddr->setEditable(true);  //下拉框可编辑输入
    m_pComboAddr->addItem("239.0.0.1");
    // 正则匹配 D类IP:224.0.0.0~239.255.255.255
    // .必须使用转义字符\,否则.会匹配任意字符
    // C++中"\"在字符串中表示要用"\\"
    // 是 - 不是 ~ ; 是[0-9]不是[0~9]
    QRegExp regExp("^(22[4-9]|23[0-9])(\\.((\\d)|([1-9]\\d)|(1\\d{2})|(2[0-4]\\d)|(25[0-5]))){3}$");
    QValidator* pValidator = new QRegExpValidator(regExp, this);
    m_pComboAddr->setValidator(pValidator);
    QHBoxLayout* HLay1 = new QHBoxLayout();
    HLay1->addWidget(m_pLabPort, 1, Qt::AlignRight);
    HLay1->addWidget(m_pSpinPort, 1);
    HLay1->addWidget(m_pLabAddr, 1, Qt::AlignRight);
    HLay1->addWidget(m_pComboAddr, 2);
    m_pLineEdit = new QLineEdit(m_pCentralWidget);
    m_pBtnSendMulti = new QPushButton(QStringLiteral("组播消息"), m_pCentralWidget);
    QHBoxLayout* HLay2 = new QHBoxLayout();
    HLay2->addWidget(m_pLineEdit, 4);
    HLay2->addWidget(m_pBtnSendMulti, 1);
    m_pPlainText = new QPlainTextEdit(m_pCentralWidget);
    QVBoxLayout* VLay = new QVBoxLayout(m_pCentralWidget);
    VLay->addLayout(HLay1);
    VLay->addLayout(HLay2);
    VLay->addWidget(m_pPlainText);
    this->setCentralWidget(m_pCentralWidget);
    this->setLayout(VLay);

    //状态栏
    m_pLabState = new QLabel(QStringLiteral("socket状态:"), this);
    m_pLabState->setMinimumWidth(150);
    ui->statusBar->addWidget(m_pLabState);

    QString str = getLocalIP();
    this->setWindowTitle(this->windowTitle() + "---IP:" + str);

    m_pUdpSocket = new QUdpSocket(this);
    m_pUdpSocket->setSocketOption(QAbstractSocket::MulticastTtlOption, 1);

    // connect
    connect(m_pActJoinMulti, &QAction::triggered, this, &MainWindow::slotActJoinMulti);
    connect(m_pActLeaveMulti, &QAction::triggered, this, &MainWindow::slotActLeaveMulti);
    connect(m_pActClearText, &QAction::triggered, this, &MainWindow::slotActClearText);
    connect(m_pActQuit, &QAction::triggered, this, &MainWindow::slotActQuit);
    connect(m_pUdpSocket, &QUdpSocket::stateChanged, this, &MainWindow::slotSocketStateChanged);
    connect(m_pBtnSendMulti, &QPushButton::clicked, this, &MainWindow::slotBtnMultiMsg);
    connect(m_pUdpSocket, &QUdpSocket::readyRead, this, &MainWindow::slotReadyRead);
}

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

void MainWindow::slotActJoinMulti() {
    QString ip = m_pComboAddr->currentText();
    m_multicastAddr = QHostAddress(ip);
    quint16 multicastPort = m_pSpinPort->value();
    if (m_pUdpSocket->bind(QHostAddress::AnyIPv4, multicastPort, QUdpSocket::ShareAddress)) {
        m_pUdpSocket->joinMulticastGroup(m_multicastAddr);  //加入多播组
        m_pPlainText->appendPlainText("**加入组播成功");
        m_pPlainText->appendPlainText("**组播地址IP:" + ip);
        m_pPlainText->appendPlainText("**绑定端口:" + QString::number(multicastPort));
        m_pActJoinMulti->setEnabled(false);
        m_pActLeaveMulti->setEnabled(true);
        m_pComboAddr->setEditable(false);
    } else {
        m_pPlainText->appendPlainText("**绑定端口失败");
    }
}

void MainWindow::slotActLeaveMulti() {
    m_pUdpSocket->leaveMulticastGroup(m_multicastAddr);  //退出组播
    m_pUdpSocket->abort();                               //解除绑定
    m_pActJoinMulti->setEnabled(true);
    m_pActLeaveMulti->setEnabled(false);
    m_pComboAddr->setEnabled(true);
    m_pComboAddr->setEditable(true);
    m_pPlainText->appendPlainText("**已退出组播,解除端口绑定");
}

void MainWindow::slotActClearText() { m_pPlainText->clear(); }

void MainWindow::slotActQuit() {
    QMessageBox::StandardButton button = QMessageBox::question(this, "", "是否退出?");
    if (QMessageBox::StandardButton::Yes == button) {
        this->close();
    }
}

void MainWindow::slotSocketStateChanged(QAbstractSocket::SocketState socketState) {
    // case并不包含所有的情况,因为没有写listening的情况,所以就需要写default
    switch (socketState) {
        case QAbstractSocket::UnconnectedState: m_pLabState->setText("socket状态:UnconnectedState"); break;
        case QAbstractSocket::HostLookupState: m_pLabState->setText("socket状态:HostLookupState"); break;
        case QAbstractSocket::ConnectingState: m_pLabState->setText("socket状态:ConnectingState"); break;
        case QAbstractSocket::ConnectedState: m_pLabState->setText("socket状态:ConnectedState"); break;
        case QAbstractSocket::BoundState: m_pLabState->setText("socket状态:BoundState"); break;
        case QAbstractSocket::ClosingState: m_pLabState->setText("socket状态:ClosingState"); break;
        default: break;
    }
}

void MainWindow::slotBtnMultiMsg() {
    QString msg = m_pLineEdit->text();
    QByteArray str = msg.toUtf8();
    quint16 multiPort = m_pSpinPort->value();
    m_pUdpSocket->writeDatagram(str, m_multicastAddr, multiPort);
    m_pPlainText->appendPlainText("[multicast] " + msg);
    m_pLineEdit->clear();
    m_pLineEdit->setFocus();
}

void MainWindow::slotReadyRead() {
    while (m_pUdpSocket->hasPendingDatagrams()) {
        QByteArray dataGram;
        dataGram.resize(m_pUdpSocket->pendingDatagramSize());
        QHostAddress peerAddr;
        quint16 peerPort;
        m_pUdpSocket->readDatagram(dataGram.data(), dataGram.size(), &peerAddr, &peerPort);
        QString str = dataGram.data();
        QString peer = "[From " + peerAddr.toString() + ":" + QString::number(peerPort) + "] ";
        m_pPlainText->appendPlainText(peer + str);
        qDebug() << m_pUdpSocket->peerAddress();
        qDebug() << m_pUdpSocket->localAddress().toString();
        qDebug() << m_pUdpSocket->localPort();
    }
}

QString MainWindow::getLocalIP() {
    QString localName = QHostInfo::localHostName();
    QHostInfo hostInfo = QHostInfo::fromName(localName);
    QList<QHostAddress> addrList = hostInfo.addresses();
    QString localIP = "";
    if (!addrList.isEmpty()) {
        for (int i = 0; i < addrList.size(); i++) {
            QHostAddress addr = addrList.at(i);
            if (QAbstractSocket::IPv4Protocol == addr.protocol()) {
                localIP = addr.toString();
                break;
            }
        }
    }
    return localIP;
}

结果: 

原文链接:https://blog.csdn.net/WL0616/article/details/129050373 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/962724.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringCloudAlibaba Gateway(一)简单集成

SpringCloudAlibaba Gateway(一)简单集成 随着服务模块的增加&#xff0c;一定会产生多个接口地址&#xff0c;那么客户端调用多个接口只能使用多个地址&#xff0c;维护多个地址是很不方便的&#xff0c;这个时候就需要统一服务地址。同时也可以进行统一认证鉴权的需求。那么服…

vcs仿真教程(查看断言)

VCS是在linux下面用来进行仿真看波形的工具&#xff0c;类似于windows下面的modelsim以及questasim等工具&#xff0c;以及quartus、vivado仿真的操作。 1.vcs的基本指令 vcs的常见指令后缀 sim常见指令 2.使用vcs的实例 &#xff08;1&#xff09;新建文件夹&#xff1a; …

linux 开设端口

1&#xff0c;查看端口情况 firewall-cmd --list-all2&#xff0c;开设端口 firewall-cmd --zonepublic --add-port6379/tcp --permanent–zonepublic 表示作用域为公共的 –add-port6379/tcp 添加 tcp 协议的端口端口号为 6379 –permanent 永久生效&#xff0c;如果没有此参…

每日一题 1372二叉树中的最长交错路径

题目 给你一棵以 root 为根的二叉树&#xff0c;二叉树中的交错路径定义如下&#xff1a; 选择二叉树中 任意 节点和一个方向&#xff08;左或者右&#xff09;。如果前进方向为右&#xff0c;那么移动到当前节点的的右子节点&#xff0c;否则移动到它的左子节点。改变前进方…

枚举的简单介绍

目录 概念&#xff1a; 枚举的声明&#xff1a; 枚举的使用&#xff1a; 枚举的取值&#xff1a; 枚举的优点&#xff1a; #define的功能&#xff1a; 而与#define对比&#xff0c;枚举的优点有&#xff1a; 概念&#xff1a; 枚举顾名思义就是⼀⼀列举。 把可能的取值…

WireShark流量抓包详解

目录 Wireshark软件安装Wireshark 开始抓包示例Wireshakr抓包界面介绍WireShark 主要界面 wireshark过滤器表达式的规则 Wireshark软件安装 软件下载路径&#xff1a;wireshark官网。按照系统版本选择下载&#xff0c;下载完成后&#xff0c;按照软件提示一路Next安装。 Wire…

CUDA小白 - NPP(2) - Arithmetic and Logical Operations(2)

cuda小白 原始API链接 NPP GPU架构近些年也有不少的变化&#xff0c;具体的可以参考别的博主的介绍&#xff0c;都比较详细。还有一些cuda中的专有名词的含义&#xff0c;可以参考《详解CUDA的Context、Stream、Warp、SM、SP、Kernel、Block、Grid》 常见的NppStatus&#xf…

【100天精通python】Day50:python web编程_Django框架从安装到使用

目录 1 安装Django Web框架 2 创建一个Django 项目 3 数据模型 3.1 在应用程序的 models.py 文件中定义数据模 3.2 创建模型的迁移文件并应用 3.2.1 查询模型对象&#xff1a; 3.2.2 创建新模型对象&#xff1a; 3.2.3 更新模型对象&#xff1a; 3.2.4 删除模型对象&a…

JUC并发编程---Lock锁

文章目录 什么是Locksynchronized加锁和Lock加锁代码示例synchronized使用Lock加锁 公平锁和非公平锁公平锁&#xff1a;非公平锁&#xff1a;Lock和Synchronized的区别 synchronized 版的生产者和消费者Lock 版的生产者和消费者生产者和消费者出现的问题Condition精准通知和唤…

机器视觉工程师,人学习最大的能力是理解与善于运用,而不是记住能力

谁记得以前记住的元素周期表&#xff0c;谁能记得住乘法口诀。 如果我们去看一眼&#xff0c;就会迅速记起来。再加上我们小学机械般的练习题。再到我们在现实生活中经常用到。 其实我们机器视觉工程师&#xff0c;一定要去看&#xff0c;还要去练习​。实操软件&#xff0c;多…

深度学习-4-二维目标检测-YOLOv5源码测试与训练

本文采用的YOLOv5源码是ultralytics发行版3.1 YOLOv5源码测试与训练 1.Anaconda环境配置 1.1安装Anaconda Anaconda 是一个用于科学计算的 Python 发行版&#xff0c;支持 Linux, Mac, Windows, 包含了众多流行的科学计算、数据分析的 Python 包。 官方网址下载安装包&…

【SQL应知应会】索引 • Oracle版:B-树索引;位图索引;函数索引;单列与复合索引;分区索引

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 本文免费学习&#xff0c;自发文起3天后&#xff0c;会收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习&#xff0c;有基础也有进阶&#xff0c;有MySQL也有Oracle …

面试被打脸,数据结构底层都不知道么--回去等通知吧

数据结构之常见的8种数据结构&#xff1a; -数组Array -链表 Linked List -堆 heap -栈 stack -队列 Queue -树 Tree -散列表 Hash -图 Graph 数据结构-链表篇 Linklist定义&#xff1a; -是一种线性表&#xff0c;并不会按线性的顺序存储数据&#xff0c;即逻辑上相邻…

解码自我注意的魔力:深入了解其直觉和机制

一、说明 自我注意机制是现代机器学习模型中的关键组成部分&#xff0c;尤其是在处理顺序数据时。这篇博文旨在提供这种机制的详细概述&#xff0c;解释它是如何工作的&#xff0c;它的优点&#xff0c;以及它背后的数学原理。我们还将讨论它在变压器模型中的实现和多头注意力的…

设计模式-10--多例模式(Multition pattern)

一、什么是多例模式&#xff08;Multition pattern&#xff09; 多例模式&#xff08;Multition pattern&#xff09;是单例模式的一种扩展&#xff0c;它属于对象创建类型的设计模式。在多例模式中&#xff0c;一个类可以有多个实例&#xff0c;并且这些实例都是该类本身。因…

实现不同局域网间的文件共享和端口映射,使用Python自带的HTTP服务

文章目录 1. 前言2. 本地文件服务器搭建2.1 python的安装和设置2.2 cpolar的安装和注册 3. 本地文件服务器的发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1. 前言 数据共享作为和连接作为互联网的基础应用&#xff0c;不仅在商业和办公场景有广泛的应用…

设计模式-5--适配器模式(Adapter Pattern)

一、什么是适配器模式&#xff08;Adapter Pattern&#xff09; 适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许将一个类的接口转换成客户端所期望的另一个接口。适配器模式主要用于解决不兼容接口之间的问题&#xff0c;使得原本…

Windows安装jdk

Windows安装jdk 小白教程&#xff0c;一看就会&#xff0c;一做就成。 1.准备安装包&#xff08;需要的滴滴我&#xff09; 2.安装 我是在d盘创建jdk目录&#xff0c;把jdk包解压到jdk里 计算机右键---属性---高级系统设置—环境变量 &#xff08;系统变量里&#xff09;--新…

小兔鲜儿 - 地址模块

目录 小兔鲜儿 - 地址模块 准备工作​ 静态结构​ 地址管理页​ 地址表单页​ 动态设置标题​ 新建地址页​ 接口封装​ 参考代码​ 地址管理页​ 接口调用​ 参考代码​ 修改地址页​ 数据回显​ 更新地址​ 表单校验​ 操作步骤​ 删除地址​ 侧滑组件用法…

Leetcode 剑指 Offer II 042. 最近的请求次数

题目难度: 简单 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 写一个 RecentCounter 类来计算特定时间范围内最近的请求。 请实…