对QUdpSocket进行封装为udp。

继承 QObject + 支持 moveToThread + 全信号槽的版本

Udp.h

cpp 复制代码
#pragma once

#include <QObject>
#include <QByteArray>
#include <QString>
#include <cstdint>

class UdpPrivate;

class Udp : public QObject
{
    Q_OBJECT
public:
    // 保持原拼写,兼容旧调用方
    enum State {
        Opend    = 0,   ///< 打开成功
        PortUsed = 1,   ///< 端口被占用
        Stoped   = 2,   ///< 已停止
    };
    Q_ENUM(State)

    explicit Udp(QObject* parent = nullptr);
    ~Udp() override;

    Udp(const Udp&) = delete;
    Udp& operator=(const Udp&) = delete;

    static const char* stateName(State s);

    // 查询(可在任意线程调用)
    uint16_t locPort(bool isreal = false) const;
    State state() const;

signals:
    // 状态变化(跨线程自动排队到接收者线程)
    void stateChanged(Udp::State s);

    // 收到数据(数据在信号里是拷贝,接收方无需担心生命周期)
    void dataReceived(const QByteArray& data, const QString& ip, quint16 port);

public slots:
    // 以下三个会做线程判断,跨线程调用会自动排队
    void setLocPort(uint16_t port);
    void start(bool autoconnect = true, int msec = 3000);
    void stop();

    // 发送:推荐用它(线程安全,QByteArray 拷贝数据)
    void sendData(const QByteArray& data, const QString& toIp, quint16 toPort);

    // 兼容原接口。同线程返回实际发送字节数,跨线程返回已排队字节数
    int send_data(char* dat, int size, QString toIP, uint16_t toPort);

private:
    UdpPrivate* m_d = nullptr;
};

Udp.cpp

c 复制代码
#include "Udp.h"

#include <QThread>
#include <QTimer>
#include <QUdpSocket>
#include <QHostAddress>
#include <QMetaObject>

// ---------------------------------------------------------------
// 内部实现
// - 作为 Udp 的 QObject 子对象存在,moveToThread 时会一起搬过去
// - 所有网络操作都在 Udp 所在线程执行
// ---------------------------------------------------------------
class UdpPrivate : public QObject
{
public:
    explicit UdpPrivate(Udp* owner)
        : QObject(owner)
        , owner(owner)
    {
        socket = new QUdpSocket(this);
        retryTimer = new QTimer(this);
        retryTimer->setSingleShot(true);

        // 这里的 this 是 QObject*,作为 context 保证连接在对象线程执行
        QObject::connect(socket, &QUdpSocket::readyRead,
                         this, [this]() { onReadyRead(); });
        QObject::connect(retryTimer, &QTimer::timeout,
                         this, [this]() { tryBind(); });
    }

    Udp* owner = nullptr;

    QUdpSocket* socket = nullptr;
    QTimer* retryTimer = nullptr;

    uint16_t locPort = 0;
    uint16_t locRealPort = 0;

    bool autoconnect = true;
    int  retryMs = 3000;

    Udp::State state = Udp::Stoped;

    void setState(Udp::State s)
    {
        if (state == s) return;
        state = s;
        emit owner->stateChanged(s);
    }

    bool tryBind()
    {
        if (socket->state() == QAbstractSocket::BoundState) {
            return true;
        }

        // locPort == 0 时,Qt 自动分配随机端口
        if (!socket->bind(QHostAddress::AnyIPv4, locPort)) {
            if (socket->error() == QAbstractSocket::AddressInUseError) {
                setState(Udp::PortUsed);
                if (autoconnect && retryMs > 0) {
                    retryTimer->start(retryMs);
                }
            } else {
                setState(Udp::Stoped);
            }
            return false;
        }

        locRealPort = socket->localPort();
        setState(Udp::Opend);
        return true;
    }

    void onReadyRead()
    {
        while (socket->hasPendingDatagrams()) {
            const qint64 pending = socket->pendingDatagramSize();
            if (pending <= 0) break;

            QByteArray buf;
            buf.resize(static_cast<int>(pending));

            QHostAddress sender;
            quint16 senderPort = 0;
            const qint64 n = socket->readDatagram(
                buf.data(), buf.size(), &sender, &senderPort);
            if (n < 0) continue;

            buf.resize(static_cast<int>(n));
            emit owner->dataReceived(buf, sender.toString(), senderPort);
        }
    }

    int doSend(const char* dat, int size, const QString& toIP, quint16 toPort)
    {
        if (state != Udp::Opend) return -1;
        if (dat == nullptr || size <= 0) return -1;

        QHostAddress addr;
        if (!addr.setAddress(toIP)) return -1;

        const qint64 n = socket->writeDatagram(dat, size, addr, toPort);
        return static_cast<int>(n);
    }
};

// ---------------------------------------------------------------
// Udp 公开接口
// ---------------------------------------------------------------

const char* Udp::stateName(Udp::State s)
{
    switch (s) {
    case Opend:    return "Opend";
    case PortUsed: return "PortUsed";
    case Stoped:   return "Stoped";
    }
    return "Unknown";
}

Udp::Udp(QObject* parent)
    : QObject(parent)
    , m_d(new UdpPrivate(this))   // 作为子对象,moveToThread 时一起搬
{
}

Udp::~Udp()
{
    // 让底层先收拾干净;剩下交给 QObject 父子关系自动 delete
    m_d->retryTimer->stop();
    if (m_d->socket->state() != QAbstractSocket::UnconnectedState) {
        m_d->socket->close();
    }
}

uint16_t Udp::locPort(bool isreal) const
{
    return isreal ? m_d->locRealPort : m_d->locPort;
}

Udp::State Udp::state() const
{
    return m_d->state;
}

// ---------------- slots ----------------

void Udp::setLocPort(uint16_t port)
{
    if (QThread::currentThread() != thread()) {
        QMetaObject::invokeMethod(this, [this, port]() {
            m_d->locPort = port;
        }, Qt::QueuedConnection);
        return;
    }
    m_d->locPort = port;
}

void Udp::start(bool autoconnect, int msec)
{
    if (QThread::currentThread() != thread()) {
        QMetaObject::invokeMethod(this, [this, autoconnect, msec]() {
            start(autoconnect, msec);
        }, Qt::QueuedConnection);
        return;
    }

    m_d->autoconnect = autoconnect;
    m_d->retryMs = msec > 0 ? msec : 3000;

    if (m_d->socket->state() == QAbstractSocket::BoundState) {
        m_d->socket->close();
        m_d->state = Udp::Stoped;
    }

    m_d->retryTimer->stop();
    m_d->tryBind();
}

void Udp::stop()
{
    if (QThread::currentThread() != thread()) {
        QMetaObject::invokeMethod(this, [this]() {
            stop();
        }, Qt::QueuedConnection);
        return;
    }

    m_d->retryTimer->stop();
    if (m_d->socket->state() != QAbstractSocket::UnconnectedState) {
        m_d->socket->close();
    }
    m_d->setState(Udp::Stoped);
}

void Udp::sendData(const QByteArray& data, const QString& toIp, quint16 toPort)
{
    if (data.isEmpty()) return;

    if (QThread::currentThread() != thread()) {
        QMetaObject::invokeMethod(this, [this, data, toIp, toPort]() {
            m_d->doSend(data.constData(), data.size(), toIp, toPort);
        }, Qt::QueuedConnection);
        return;
    }
    m_d->doSend(data.constData(), data.size(), toIp, toPort);
}

int Udp::send_data(char* dat, int size, QString toIP, uint16_t toPort)
{
    if (dat == nullptr || size <= 0) return -1;

    if (QThread::currentThread() != thread()) {
        QByteArray copy(dat, size);   // 拷贝,避免悬垂指针
        const QString ip = toIP;
        QMetaObject::invokeMethod(this, [this, copy, ip, toPort]() {
            m_d->doSend(copy.constData(), copy.size(), ip, toPort);
        }, Qt::QueuedConnection);
        return size;   // 已排队
    }

    return m_d->doSend(dat, size, toIP, toPort);
}
c 复制代码
QT += core network

调用方法

cpp 复制代码
#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include <QDebug>
#include "Udp.h"

int main(int argc, char* argv[])
{
    QCoreApplication app(argc, argv);

    // 1. 创建工作线程
    QThread* worker = new QThread;
    worker->setObjectName("UdpThread");
    app.connect(worker, &QThread::finished, worker, &QObject::deleteLater);

    // 2. 创建 Udp,注意 parent 必须为 nullptr,否则 moveToThread 会失败
    Udp* udp = new Udp;
    udp->moveToThread(worker);

    // 3. 收到数据(自动排队回主线程,UI 更新安全)
    QObject::connect(udp, &Udp::dataReceived,
                     [](const QByteArray& data, const QString& ip, quint16 port) {
        qDebug() << "[主线程]" << QThread::currentThread()
                 << "收到" << ip << port << data;
    });

    QObject::connect(udp, &Udp::stateChanged,
                     [](Udp::State s) {
        qDebug() << "状态:" << Udp::stateName(s);
    });

    // 4. 线程启动后,在子线程里打开串口
    QObject::connect(worker, &QThread::started, udp, [udp]() {
        udp->setLocPort(8888);
        udp->start();
    });

    worker->start();

    // 5. 主线程发一条(内部会自动排队到 Udp 所在线程)
    QTimer::singleShot(1500, [udp]() {
        udp->sendData("hello from main", "127.0.0.1", 9999);
    });

    // 6. 收尾
    QTimer::singleShot(5000, [&]() {
        // 跨线程调用,队列执行
        udp->stop();
        worker->quit();
        worker->wait();
        udp->deleteLater();
        app.quit();
    });

    return app.exec();
}
相关推荐
Multipath7123 小时前
多链路聚合设备:助力智慧农业发展的网络基石——让农田网络不断连,让智能设备不失控
网络·网络协议·5g·udp·无人机
似是燕归来3 小时前
Rtthread OS 功能开发之 socket udp协议程序开发
网络·stm32·网络协议·udp·socket·rtthread
打工仔折腾 AI1 天前
Qt网络编程实战:UDP、TCP到HTTP的落地经验与选型
网络·人工智能·后端·python·qt·udp·ai agent 实战
沫璃染墨1 天前
从零入门计算机网络系列(五):UDP套接字编程实战——从核心API到客户端与服务器通信
linux·网络·c++·vscode·网络协议·udp
默|笙2 天前
【Linux网络】_udp_socket(认识)
linux·网络·udp
上海云盾-小余2 天前
反射放大攻击底层拆解:UDP 反射攻击的识别与防御思路
网络·网络协议·udp
流浪0012 天前
计算机网络篇5:UDP 收发接口 recvfrom 与 sendto 的参数语义
计算机网络·udp·sendto·recvfrom
Escalating_xu2 天前
【Linux 网络编程】UDP Socket 编程(下):回调封装、网络字典与多线程聊天室
linux·网络·udp