基本通讯框架构建
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
*.pro.*
|
||||
*.bak
|
||||
*.asv
|
||||
build/
|
||||
*~
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
QT += core gui
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets core network serialport
|
||||
|
||||
|
||||
CONFIG += c++17
|
||||
|
||||
# You can make your code fail to compile if it uses deprecated APIs.
|
||||
# In order to do so, uncomment the following line.
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
|
||||
# 头文件搜索路径
|
||||
INCLUDEPATH += \
|
||||
$$PWD/src \
|
||||
$$PWD/src/core \
|
||||
$$PWD/src/ui
|
||||
|
||||
SOURCES += \
|
||||
src/core/Injector.cpp \
|
||||
src/core/MavlinkTap.cpp \
|
||||
src/core/ProxyConnection.cpp \
|
||||
src/core/ProxyService.cpp \
|
||||
src/core/SerialEndpoint.cpp \
|
||||
src/core/UdpHubEndpoint.cpp \
|
||||
src/main.cpp \
|
||||
src/ui/mainwindow.cpp
|
||||
|
||||
HEADERS += \
|
||||
src/core/Endpoint.hpp \
|
||||
src/core/Injector.hpp \
|
||||
src/core/MavlinkTap.hpp \
|
||||
src/core/ProxyConnection.hpp \
|
||||
src/core/ProxyService.hpp \
|
||||
src/core/SerialEndpoint.hpp \
|
||||
src/core/UdpHubEndpoint.hpp \
|
||||
src/ui/mainwindow.h
|
||||
|
||||
FORMS += \
|
||||
src/ui/mainwindow.ui
|
||||
|
||||
# Default rules for deployment.
|
||||
qnx: target.path = /tmp/$${TARGET}/bin
|
||||
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||
!isEmpty(target.path): INSTALLS += target
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <functional>
|
||||
|
||||
class Endpoint : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using RxHandler = std::function<void(const QByteArray&)>;
|
||||
explicit Endpoint(QObject* parent = nullptr) : QObject(parent) {}
|
||||
virtual ~Endpoint() = default;
|
||||
|
||||
virtual bool open() = 0;
|
||||
virtual void close() = 0;
|
||||
virtual bool isOpen() const = 0;
|
||||
virtual qint64 writeBytes(const QByteArray& data) = 0;
|
||||
|
||||
void setRxHandler(RxHandler cb) { rx_ = std::move(cb); }
|
||||
|
||||
protected:
|
||||
void emitRx(const QByteArray& data) { if (rx_) rx_(data); }
|
||||
|
||||
private:
|
||||
RxHandler rx_;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "Injector.hpp"
|
||||
|
||||
Injector::Injector(Endpoint* target, uint8_t sysid, uint8_t compid, QObject* parent)
|
||||
: QObject(parent), target_(target), sysid_(sysid), compid_(compid) {}
|
||||
|
||||
bool Injector::sendHeartbeat(uint8_t type, uint8_t autopilot, uint8_t base_mode,
|
||||
uint32_t custom_mode, uint8_t system_status) {
|
||||
if (!target_ || !target_->isOpen()) return false;
|
||||
mavlink_message_t msg;
|
||||
mavlink_msg_heartbeat_pack(sysid_, compid_, &msg, type, autopilot, base_mode, custom_mode, system_status);
|
||||
uint8_t buf[MAVLINK_MAX_PACKET_LEN];
|
||||
const int len = mavlink_msg_to_send_buffer(buf, &msg);
|
||||
return target_->writeBytes(QByteArray(reinterpret_cast<const char*>(buf), len)) == len;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
#include "Endpoint.hpp"
|
||||
extern "C" {
|
||||
#include "mavlink/common/mavlink.h"
|
||||
}
|
||||
|
||||
class Injector : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Injector(Endpoint* target, uint8_t sysid, uint8_t compid, QObject* parent = nullptr);
|
||||
bool sendHeartbeat(uint8_t type, uint8_t autopilot, uint8_t base_mode,
|
||||
uint32_t custom_mode, uint8_t system_status);
|
||||
|
||||
private:
|
||||
Endpoint* target_;
|
||||
uint8_t sysid_;
|
||||
uint8_t compid_;
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "MavlinkTap.hpp"
|
||||
#include <cstring>
|
||||
|
||||
MavlinkTap::MavlinkTap(QObject* parent) : QObject(parent) {
|
||||
std::memset(&status_, 0, sizeof(status_));
|
||||
}
|
||||
|
||||
void MavlinkTap::feed(const QByteArray& data) {
|
||||
QMutexLocker locker(&mtx_);
|
||||
mavlink_message_t msg;
|
||||
for (unsigned char c : data) {
|
||||
if (mavlink_parse_char(MAVLINK_COMM_0, c, &msg, &status_)) {
|
||||
emit messageParsed(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QMutex>
|
||||
|
||||
extern "C" {
|
||||
#include "mavlink/common/mavlink.h"
|
||||
}
|
||||
|
||||
class MavlinkTap : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MavlinkTap(QObject* parent = nullptr);
|
||||
void feed(const QByteArray& data); // 喂入原始字节(任意方向)
|
||||
|
||||
signals:
|
||||
void messageParsed(const mavlink_message_t& msg);
|
||||
|
||||
private:
|
||||
QMutex mtx_;
|
||||
mavlink_status_t status_;
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "ProxyConnection.hpp"
|
||||
#include <QDebug>
|
||||
|
||||
ProxyConnection::ProxyConnection(Endpoint* a, Endpoint* b, MavlinkTap* tap, QObject* parent)
|
||||
: QObject(parent), A_(a), B_(b), tap_(tap) {}
|
||||
|
||||
bool ProxyConnection::start() {
|
||||
if (!A_ || !B_) return false;
|
||||
if (!A_->open() || !B_->open()) {
|
||||
qWarning() << "ProxyConnection: failed to open endpoints";
|
||||
return false;
|
||||
}
|
||||
running_ = true;
|
||||
|
||||
A_->setRxHandler([this](const QByteArray& data){
|
||||
if (!running_) return;
|
||||
if (tap_) tap_->feed(data); // 仅监控解析
|
||||
auto n = B_->writeBytes(data); // 原样转发
|
||||
emit bytesForwardedAB(n);
|
||||
});
|
||||
|
||||
B_->setRxHandler([this](const QByteArray& data){
|
||||
if (!running_) return;
|
||||
if (tap_) tap_->feed(data);
|
||||
auto n = A_->writeBytes(data);
|
||||
emit bytesForwardedBA(n);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProxyConnection::stop() {
|
||||
running_ = false;
|
||||
if (A_->isOpen()) A_->close();
|
||||
if (B_->isOpen()) B_->close();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
#include "Endpoint.hpp"
|
||||
#include "MavlinkTap.hpp"
|
||||
|
||||
class ProxyConnection : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ProxyConnection(Endpoint* a, Endpoint* b, MavlinkTap* tap = nullptr, QObject* parent = nullptr);
|
||||
~ProxyConnection() override = default;
|
||||
|
||||
bool start();
|
||||
void stop();
|
||||
|
||||
signals:
|
||||
void bytesForwardedAB(qint64 n);
|
||||
void bytesForwardedBA(qint64 n);
|
||||
|
||||
private:
|
||||
Endpoint* A_;
|
||||
Endpoint* B_;
|
||||
MavlinkTap* tap_;
|
||||
bool running_ = false;
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
#include "ProxyService.hpp"
|
||||
#include <QMetaObject>
|
||||
#include <QDebug>
|
||||
|
||||
ProxyService::ProxyService(const QString& serialPort, qint32 baudRate, quint16 gcsListenPort, QObject* parent)
|
||||
: QObject(parent),
|
||||
serialPort_(serialPort),
|
||||
baudRate_(baudRate),
|
||||
gcsPort_(gcsListenPort)
|
||||
{
|
||||
// 预创建对象(仍在主线程),随后统一迁移到IO线程
|
||||
serial_ = new SerialEndpoint(serialPort_, baudRate_);
|
||||
udpHub_ = new UdpHubEndpoint(gcsPort_);
|
||||
tap_ = new MavlinkTap;
|
||||
proxy_ = new ProxyConnection(serial_, udpHub_, tap_);
|
||||
injector_ = new Injector(udpHub_, /*sysid*/245, /*compid*/190);
|
||||
|
||||
// 迁移到 IO 线程(这里只是“归属”变更,不做 open/bind)
|
||||
serial_->moveToThread(&ioThread_);
|
||||
udpHub_->moveToThread(&ioThread_);
|
||||
tap_->moveToThread(&ioThread_);
|
||||
proxy_->moveToThread(&ioThread_);
|
||||
injector_->moveToThread(&ioThread_);
|
||||
|
||||
// 将 IO 线程产生的信号桥接到主线程
|
||||
connect(tap_, &MavlinkTap::messageParsed, this, &ProxyService::messageParsed, Qt::QueuedConnection);
|
||||
connect(udpHub_, &UdpHubEndpoint::peerAdded, this, &ProxyService::peerAdded, Qt::QueuedConnection);
|
||||
connect(udpHub_, &UdpHubEndpoint::peerRemoved,this,&ProxyService::peerRemoved, Qt::QueuedConnection);
|
||||
connect(proxy_, &ProxyConnection::bytesForwardedAB, this, &ProxyService::bytesForwardedAB, Qt::QueuedConnection);
|
||||
connect(proxy_, &ProxyConnection::bytesForwardedBA, this, &ProxyService::bytesForwardedBA, Qt::QueuedConnection);
|
||||
|
||||
// 线程结束时通知
|
||||
connect(&ioThread_, &QThread::finished, this, [this](){
|
||||
emit stopped();
|
||||
});
|
||||
|
||||
// 线程启动时在 IO 线程执行 ioStart_
|
||||
connect(&ioThread_, &QThread::started, this, &ProxyService::ioStart_, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
ProxyService::~ProxyService() {
|
||||
stop();
|
||||
ioThread_.wait();
|
||||
// 对象都在IO线程归属;此时线程已退出,可安全删除
|
||||
delete serial_;
|
||||
delete udpHub_;
|
||||
delete tap_;
|
||||
delete proxy_;
|
||||
delete injector_;
|
||||
}
|
||||
|
||||
void ProxyService::start() {
|
||||
if (running_) return;
|
||||
ioThread_.start();
|
||||
}
|
||||
|
||||
void ProxyService::stop() {
|
||||
if (!running_) return;
|
||||
// 在 IO 线程里做关闭
|
||||
QMetaObject::invokeMethod(this, "ioStop_", Qt::QueuedConnection);
|
||||
ioThread_.quit();
|
||||
}
|
||||
|
||||
void ProxyService::setPeerIdleTimeoutMs(int ms) {
|
||||
peerIdleMs_ = ms;
|
||||
if (udpHub_) {
|
||||
QMetaObject::invokeMethod(udpHub_, [this](){
|
||||
udpHub_->setPeerIdleTimeoutMs(peerIdleMs_);
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyService::addStaticPeer(const QHostAddress& addr, quint16 port) {
|
||||
if (udpHub_) {
|
||||
QMetaObject::invokeMethod(udpHub_, [this, addr, port](){
|
||||
udpHub_->addStaticPeer(addr, port);
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyService::sendHeartbeat(uint8_t type, uint8_t autopilot,
|
||||
uint8_t base_mode, uint32_t custom_mode,
|
||||
uint8_t system_status) {
|
||||
if (injector_) {
|
||||
QMetaObject::invokeMethod(injector_, [=](){
|
||||
injector_->sendHeartbeat(type, autopilot, base_mode, custom_mode, system_status);
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- IO 线程内的实际开启/关闭 ----------------
|
||||
|
||||
void ProxyService::ioStart_() {
|
||||
// 应用 UDP Hub 的超时配置
|
||||
udpHub_->setPeerIdleTimeoutMs(peerIdleMs_);
|
||||
|
||||
// 在 IO 线程里启动透明转发(内部会 open 串口/绑定 UDP)
|
||||
const bool ok = proxy_->start();
|
||||
running_ = ok;
|
||||
if (!ok) {
|
||||
qCritical() << "[ProxyService] start failed (serial or udp bind open failed)";
|
||||
emit started(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 可选:给所有 GCS 发送一个心跳,证明代理在线(可注释)
|
||||
injector_->sendHeartbeat(6 /*MAV_TYPE_GCS*/, 8 /*MAV_AUTOPILOT_INVALID*/,
|
||||
0 /*base_mode*/, 0 /*custom*/, 4 /*system_status:active*/);
|
||||
|
||||
emit started(true);
|
||||
}
|
||||
|
||||
void ProxyService::ioStop_() {
|
||||
if (!running_) return;
|
||||
proxy_->stop(); // 内部会 close 串口与 UDP
|
||||
running_ = false;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QHostAddress>
|
||||
#include <QString>
|
||||
|
||||
#include "SerialEndpoint.hpp"
|
||||
#include "UdpHubEndpoint.hpp"
|
||||
#include "ProxyConnection.hpp"
|
||||
#include "MavlinkTap.hpp"
|
||||
#include "Injector.hpp"
|
||||
|
||||
class ProxyService : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
// 构造:只在主线程调用;不做IO操作
|
||||
explicit ProxyService(const QString& serialPort,
|
||||
qint32 baudRate,
|
||||
quint16 gcsListenPort,
|
||||
QObject* parent = nullptr);
|
||||
~ProxyService() override;
|
||||
|
||||
// 在调用线程(通常主线程)触发启动/停止;真正的开启关闭在IO线程里执行
|
||||
Q_INVOKABLE void start();
|
||||
Q_INVOKABLE void stop();
|
||||
|
||||
// 运行时控制(跨线程排队到IO线程)
|
||||
Q_INVOKABLE void setPeerIdleTimeoutMs(int ms);
|
||||
Q_INVOKABLE void addStaticPeer(const QHostAddress& addr, quint16 port);
|
||||
|
||||
// 可选:注入一个心跳(排队到IO线程执行)
|
||||
Q_INVOKABLE void sendHeartbeat(uint8_t type, uint8_t autopilot,
|
||||
uint8_t base_mode, uint32_t custom_mode,
|
||||
uint8_t system_status);
|
||||
|
||||
signals:
|
||||
// 生命周期
|
||||
void started(bool ok);
|
||||
void stopped();
|
||||
|
||||
// 观察数据
|
||||
void messageParsed(const mavlink_message_t& msg); // 从IO线程跨到主线程(Queued)
|
||||
void bytesForwardedAB(qint64 n); // drone->GCS
|
||||
void bytesForwardedBA(qint64 n); // GCS->drone
|
||||
|
||||
// GCS 动态
|
||||
void peerAdded(const QHostAddress& addr, quint16 port);
|
||||
void peerRemoved(const QHostAddress& addr, quint16 port);
|
||||
|
||||
private slots:
|
||||
// 这些槽运行在 IO 线程中
|
||||
void ioStart_(); // 打开端口/绑定socket/建立转发
|
||||
void ioStop_(); // 停止转发/关闭设备
|
||||
|
||||
private:
|
||||
// 仅在主线程访问这些裸指针的“拥有关系”;实际方法调用都在IO线程中
|
||||
QThread ioThread_;
|
||||
SerialEndpoint* serial_ = nullptr;
|
||||
UdpHubEndpoint* udpHub_ = nullptr;
|
||||
MavlinkTap* tap_ = nullptr;
|
||||
ProxyConnection* proxy_ = nullptr;
|
||||
Injector* injector_ = nullptr;
|
||||
|
||||
// 构造参数
|
||||
const QString serialPort_;
|
||||
const qint32 baudRate_;
|
||||
const quint16 gcsPort_;
|
||||
|
||||
// 配置参数(通过Queued修改)
|
||||
int peerIdleMs_ = 30'000;
|
||||
bool running_ = false;
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "SerialEndpoint.hpp"
|
||||
#include <QDebug>
|
||||
|
||||
SerialEndpoint::SerialEndpoint(const QString& portName, qint32 baudRate, QObject* parent)
|
||||
: Endpoint(parent)
|
||||
{
|
||||
port_.setPortName(portName);
|
||||
port_.setBaudRate(baudRate);
|
||||
port_.setDataBits(QSerialPort::Data8);
|
||||
port_.setParity(QSerialPort::NoParity);
|
||||
port_.setStopBits(QSerialPort::OneStop);
|
||||
port_.setFlowControl(QSerialPort::NoFlowControl);
|
||||
|
||||
QObject::connect(&port_, &QSerialPort::readyRead, this, [this](){
|
||||
QByteArray data = port_.readAll();
|
||||
emitRx(data);
|
||||
});
|
||||
}
|
||||
|
||||
bool SerialEndpoint::open() {
|
||||
if (port_.isOpen()) return true;
|
||||
bool ok = port_.open(QIODevice::ReadWrite);
|
||||
if (!ok) qWarning() << "SerialEndpoint: failed to open" << port_.portName();
|
||||
return ok;
|
||||
}
|
||||
|
||||
void SerialEndpoint::close() {
|
||||
if (port_.isOpen()) port_.close();
|
||||
}
|
||||
|
||||
qint64 SerialEndpoint::writeBytes(const QByteArray& data) {
|
||||
if (!port_.isOpen()) return -1;
|
||||
qint64 n = port_.write(data);
|
||||
port_.waitForBytesWritten(200);
|
||||
return n;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include "Endpoint.hpp"
|
||||
#include <QSerialPort>
|
||||
|
||||
class SerialEndpoint : public Endpoint {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SerialEndpoint(const QString& portName, qint32 baudRate, QObject* parent = nullptr);
|
||||
~SerialEndpoint() override = default;
|
||||
|
||||
bool open() override;
|
||||
void close() override;
|
||||
bool isOpen() const override { return port_.isOpen(); }
|
||||
qint64 writeBytes(const QByteArray& data) override;
|
||||
|
||||
private:
|
||||
QSerialPort port_;
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
#include "UdpHubEndpoint.hpp"
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
|
||||
UdpHubEndpoint::UdpHubEndpoint(quint16 bindPort, QObject* parent)
|
||||
: Endpoint(parent), bindPort_(bindPort)
|
||||
{
|
||||
QObject::connect(&gcTimer_, &QTimer::timeout, this, &UdpHubEndpoint::gcPeers_);
|
||||
gcTimer_.setInterval(2000);
|
||||
}
|
||||
|
||||
bool UdpHubEndpoint::open() {
|
||||
if (sock_.state() == QAbstractSocket::BoundState) return true;
|
||||
bool ok = sock_.bind(QHostAddress::AnyIPv4, bindPort_, QUdpSocket::ShareAddress);
|
||||
if (!ok) {
|
||||
qWarning() << "UdpHubEndpoint: bind failed on port" << bindPort_;
|
||||
return false;
|
||||
}
|
||||
QObject::connect(&sock_, &QUdpSocket::readyRead, this, &UdpHubEndpoint::onReadyRead_);
|
||||
gcTimer_.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
void UdpHubEndpoint::close() {
|
||||
gcTimer_.stop();
|
||||
sock_.close();
|
||||
peers_.clear();
|
||||
lastSeenMs_.clear();
|
||||
}
|
||||
|
||||
bool UdpHubEndpoint::isOpen() const {
|
||||
return sock_.state() == QAbstractSocket::BoundState;
|
||||
}
|
||||
|
||||
qint64 UdpHubEndpoint::writeBytes(const QByteArray& data) {
|
||||
qint64 total = 0;
|
||||
for (const auto& p : peers_) {
|
||||
qint64 n = sock_.writeDatagram(data, p.addr, p.port);
|
||||
if (n > 0) total += n;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
void UdpHubEndpoint::addStaticPeer(const QHostAddress& addr, quint16 port) {
|
||||
upsertPeer_(addr, port);
|
||||
}
|
||||
|
||||
void UdpHubEndpoint::onReadyRead_() {
|
||||
while (sock_.hasPendingDatagrams()) {
|
||||
QByteArray buf;
|
||||
buf.resize(int(sock_.pendingDatagramSize()));
|
||||
QHostAddress sender; quint16 senderPort = 0;
|
||||
sock_.readDatagram(buf.data(), buf.size(), &sender, &senderPort);
|
||||
|
||||
upsertPeer_(sender, senderPort); // 记录/刷新该 GCS
|
||||
emitRx(buf); // 把字节给上层转发到串口
|
||||
}
|
||||
}
|
||||
|
||||
void UdpHubEndpoint::gcPeers_() {
|
||||
const qint64 now = QDateTime::currentMSecsSinceEpoch();
|
||||
QList<UdpPeer> expired;
|
||||
for (auto it = lastSeenMs_.cbegin(); it != lastSeenMs_.cend(); ++it) {
|
||||
if (now - it.value() > idleMs_) expired.push_back(it.key());
|
||||
}
|
||||
for (const auto& p : expired) {
|
||||
peers_.remove(p);
|
||||
lastSeenMs_.remove(p);
|
||||
emit peerRemoved(p.addr, p.port);
|
||||
}
|
||||
}
|
||||
|
||||
void UdpHubEndpoint::upsertPeer_(const QHostAddress& a, quint16 p) {
|
||||
UdpPeer peer{a, p};
|
||||
|
||||
bool inserted = false;
|
||||
if (!peers_.contains(peer)) {
|
||||
peers_.insert(peer); // 返回迭代器,但我们不用
|
||||
inserted = true;
|
||||
}
|
||||
|
||||
lastSeenMs_[peer] = QDateTime::currentMSecsSinceEpoch();
|
||||
if (inserted) {
|
||||
emit peerAdded(a, p);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
#include "Endpoint.hpp"
|
||||
#include <QUdpSocket>
|
||||
#include <QHostAddress>
|
||||
#include <QSet>
|
||||
#include <QMap>
|
||||
#include <QTimer>
|
||||
|
||||
struct UdpPeer {
|
||||
QHostAddress addr;
|
||||
quint16 port;
|
||||
bool operator==(const UdpPeer& o) const { return addr == o.addr && port == o.port; }
|
||||
};
|
||||
inline uint qHash(const UdpPeer& p, uint seed = 0) {
|
||||
return qHash(p.addr.toString(), seed) ^ qHash(p.port, seed);
|
||||
}
|
||||
|
||||
class UdpHubEndpoint : public Endpoint {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UdpHubEndpoint(quint16 bindPort, QObject* parent = nullptr);
|
||||
|
||||
bool open() override;
|
||||
void close() override;
|
||||
bool isOpen() const override;
|
||||
|
||||
// 广播:把 data 下发给所有已知 GCS peers
|
||||
qint64 writeBytes(const QByteArray& data) override;
|
||||
|
||||
// 可选:预置白名单
|
||||
void addStaticPeer(const QHostAddress& addr, quint16 port);
|
||||
void setPeerIdleTimeoutMs(int ms) { idleMs_ = ms; }
|
||||
|
||||
signals:
|
||||
void peerAdded(const QHostAddress& addr, quint16 port);
|
||||
void peerRemoved(const QHostAddress& addr, quint16 port);
|
||||
|
||||
private slots:
|
||||
void onReadyRead_();
|
||||
void gcPeers_();
|
||||
|
||||
private:
|
||||
void upsertPeer_(const QHostAddress& a, quint16 p);
|
||||
|
||||
QUdpSocket sock_;
|
||||
quint16 bindPort_;
|
||||
QSet<UdpPeer> peers_;
|
||||
QMap<UdpPeer, qint64> lastSeenMs_;
|
||||
QTimer gcTimer_;
|
||||
int idleMs_ = 30'000; // 30s 无流量剔除
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "mainwindow.h"
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
MainWindow w;
|
||||
w.show();
|
||||
return a.exec();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "mainwindow.h"
|
||||
#include "ui_mainwindow.h"
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent)
|
||||
, ui(new Ui::MainWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui {
|
||||
class MainWindow;
|
||||
}
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
private:
|
||||
Ui::MainWindow *ui;
|
||||
};
|
||||
#endif // MAINWINDOW_H
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget"/>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
Reference in New Issue
Block a user