继承 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();
}