7#include <QtCore/QMutexLocker>
8#include <QtCore/QThread>
9#include <QtNetwork/QHostInfo>
10#include <QtNetwork/QNetworkDatagram>
11#include <QtNetwork/QNetworkInterface>
12#include <QtNetwork/QNetworkProxy>
13#include <QtNetwork/QUdpSocket>
18 constexpr int BUFFER_TRIGGER_SIZE = 10 * 1024;
19 constexpr int RECEIVE_TIME_LIMIT_MS = 50;
21 bool containsTarget(
const QList<std::shared_ptr<UDPClient>> &list,
const QHostAddress &address, quint16 port)
23 for (
const std::shared_ptr<UDPClient> &target : list) {
24 if ((target->address == address) && (target->port == port)) {
43 qCDebug(UDPLinkLog) <<
this;
52 qCDebug(UDPLinkLog) <<
this;
59 const QString targetHostIP = settings->udpTargetHostIP()->rawValue().toString();
60 const quint16 targetHostPort = settings->udpTargetHostPort()->rawValue().toUInt();
62 setLocalPort(settings->udpListenPort()->rawValue().toInt());
63 if (!targetHostIP.isEmpty()) {
64 addHost(targetHostIP, targetHostPort);
68 if (!targetHostIP.isEmpty()) {
80 const UDPConfiguration *udpSource = qobject_cast<const UDPConfiguration*>(source);
85 for (
const std::shared_ptr<UDPClient> &target : udpSource->
targetHosts()) {
86 if (!containsTarget(_targetHosts, target->address, target->port)) {
87 _targetHosts.append(std::make_shared<UDPClient>(target.get()));
95 settings.beginGroup(root);
100 const qsizetype hostCount = settings.value(
"hostCount", 0).toUInt();
101 for (qsizetype i = 0; i < hostCount; i++) {
102 const QString hkey = QStringLiteral(
"host%1").arg(i);
103 const QString pkey = QStringLiteral(
"port%1").arg(i);
104 if (settings.contains(hkey) && settings.contains(pkey)) {
105 addHost(settings.value(hkey).toString(), settings.value(pkey).toUInt());
116 settings.beginGroup(root);
118 settings.setValue(QStringLiteral(
"hostCount"), _targetHosts.size());
119 settings.setValue(QStringLiteral(
"port"), _localPort);
121 for (qsizetype i = 0; i < _targetHosts.size(); i++) {
122 const std::shared_ptr<UDPClient> target = _targetHosts.at(i);
123 const QString hkey = QStringLiteral(
"host%1").arg(i);
124 settings.setValue(hkey, target->address.toString());
125 const QString pkey = QStringLiteral(
"port%1").arg(i);
126 settings.setValue(pkey, target->port);
134 if (host.contains(
":")) {
135 const QStringList hostInfo = host.split(
":");
136 if (hostInfo.size() != 2) {
137 qCWarning(UDPLinkLog) <<
"Invalid host format:" << host;
141 const QString address = hostInfo.constFirst();
142 const quint16 port = hostInfo.constLast().toUInt();
152 const QString ipAdd = _getIpAddress(host);
153 if (ipAdd.isEmpty()) {
154 qCWarning(UDPLinkLog) <<
"Could not resolve host:" << host <<
"port:" << port;
158 const QHostAddress address(ipAdd);
159 if (!containsTarget(_targetHosts, address, port)) {
160 _targetHosts.append(std::make_shared<UDPClient>(address, port));
167 if (host.contains(
":")) {
168 const QStringList hostInfo = host.split(
":");
169 if (hostInfo.size() != 2) {
170 qCWarning(UDPLinkLog) <<
"Invalid host format:" << host;
174 const QHostAddress address = QHostAddress(_getIpAddress(hostInfo.constFirst()));
175 const quint16 port = hostInfo.constLast().toUInt();
177 if (!containsTarget(_targetHosts, address, port)) {
178 qCWarning(UDPLinkLog) <<
"Could not remove unknown host:" << host <<
"port:" << port;
182 for (qsizetype i = 0; i < _targetHosts.size(); ++i) {
183 const std::shared_ptr<UDPClient> &target = _targetHosts[i];
184 if (target->address == address && target->port == port) {
185 _targetHosts.removeAt(i);
197 const QString ipAdd = _getIpAddress(host);
198 if (ipAdd.isEmpty()) {
199 qCWarning(UDPLinkLog) <<
"Could not resolve host:" << host <<
"port:" << port;
203 const QHostAddress address(ipAdd);
204 if (!containsTarget(_targetHosts, address, port)) {
205 qCWarning(UDPLinkLog) <<
"Could not remove unknown host:" << host <<
"port:" << port;
209 for (qsizetype i = 0; i < _targetHosts.size(); ++i) {
210 const std::shared_ptr<UDPClient> &target = _targetHosts[i];
211 if (target->address == address && target->port == port) {
212 _targetHosts.removeAt(i);
219void UDPConfiguration::_updateHostList()
222 for (
const std::shared_ptr<UDPClient> &target : _targetHosts) {
223 const QString host = target->address.toString() +
":" + QString::number(target->port);
224 _hostList.append(host);
230QString UDPConfiguration::_getIpAddress(
const QString &address)
232 const QHostAddress host(address);
233 if (!host.isNull()) {
237 const QHostInfo info = QHostInfo::fromName(address);
238 if (info.error() != QHostInfo::NoError) {
242 const QList<QHostAddress> hostAddresses = info.addresses();
243 for (
const QHostAddress &hostAddress : hostAddresses) {
244 if (hostAddress.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
245 return hostAddress.toString();
254const QHostAddress UDPWorker::_multicastGroup = QHostAddress(QStringLiteral(
"224.0.0.1"));
260 qCDebug(UDPLinkLog) <<
this;
267 qCDebug(UDPLinkLog) <<
this;
272 return (_socket && _socket->isValid() && _isConnected);
278 _socket =
new QUdpSocket(
this);
281 const QList<QHostAddress> localAddresses = QNetworkInterface::allAddresses();
282 _localAddresses = QSet(localAddresses.constBegin(), localAddresses.constEnd());
284 _socket->setProxy(QNetworkProxy::NoProxy);
286 (void) connect(_socket, &QUdpSocket::connected,
this, &UDPWorker::_onSocketConnected);
287 (void) connect(_socket, &QUdpSocket::disconnected,
this, &UDPWorker::_onSocketDisconnected);
288 (void) connect(_socket, &QUdpSocket::readyRead,
this, &UDPWorker::_onSocketReadyRead);
289 (void) connect(_socket, &QUdpSocket::errorOccurred,
this, &UDPWorker::_onSocketErrorOccurred);
290 (void) connect(_socket, &QUdpSocket::stateChanged,
this, [
this](QUdpSocket::SocketState state) {
291 qCDebug(UDPLinkLog) <<
"UDP State Changed:" << state;
293 case QAbstractSocket::BoundState:
294 _onSocketConnected();
296 case QAbstractSocket::ClosingState:
297 case QAbstractSocket::UnconnectedState:
298 _onSocketDisconnected();
305 if (UDPLinkLog().isDebugEnabled()) {
308 (void) QObject::connect(_socket, &QUdpSocket::hostFound,
this, []() {
309 qCDebug(UDPLinkLog) <<
"UDP Host Found";
317 qCWarning(UDPLinkLog) <<
"Already connected to" << _udpConfig->
localPort();
321 _errorEmitted =
false;
323 qCDebug(UDPLinkLog) <<
"Attempting to bind to port:" << _udpConfig->
localPort();
324 const bool bindSuccess = _socket->bind(QHostAddress::AnyIPv4, _udpConfig->
localPort(), QAbstractSocket::ReuseAddressHint | QAbstractSocket::ShareAddress);
326 qCWarning(UDPLinkLog) <<
"Failed to bind UDP socket to port" << _udpConfig->
localPort();
328 if (!_errorEmitted) {
330 _errorEmitted =
true;
341 qCDebug(UDPLinkLog) <<
"Attempting to join multicast group:" << _multicastGroup.toString();
342 const bool joinSuccess = _socket->joinMulticastGroup(_multicastGroup);
344 qCWarning(UDPLinkLog) <<
"Failed to join multicast group" << _multicastGroup.toString();
352 qCDebug(UDPLinkLog) <<
"Already disconnected";
356 qCDebug(UDPLinkLog) <<
"Disconnecting UDP link";
358 (void) _socket->leaveMulticastGroup(_multicastGroup);
361 _sessionTargets.clear();
367 emit
errorOccurred(tr(
"Could Not Send Data - Link is Disconnected!"));
371 QMutexLocker locker(&_sessionTargetsMutex);
374 for (
const std::shared_ptr<UDPClient> &target : _udpConfig->
targetHosts()) {
375 if (!containsTarget(_sessionTargets, target->address, target->port)) {
376 if (_socket->writeDatagram(data, target->address, target->port) < 0) {
377 qCWarning(UDPLinkLog) <<
"Could Not Send Data - Write Failed!";
383 for (
const std::shared_ptr<UDPClient> &target: _sessionTargets) {
384 if (_socket->writeDatagram(data, target->address, target->port) < 0) {
385 qCWarning(UDPLinkLog) <<
"Could Not Send Data - Write Failed!";
394void UDPWorker::_onSocketConnected()
396 qCDebug(UDPLinkLog) <<
"UDP connected to" << _udpConfig->
localPort();
398 _errorEmitted =
false;
402void UDPWorker::_onSocketDisconnected()
404 qCDebug(UDPLinkLog) <<
"UDP disconnected from" << _udpConfig->
localPort();
405 _isConnected =
false;
406 _errorEmitted =
false;
410void UDPWorker::_onSocketReadyRead()
413 emit
errorOccurred(tr(
"Could Not Read Data - Link is Disconnected!"));
417 const qint64 byteCount = _socket->pendingDatagramSize();
418 if (byteCount <= 0) {
419 emit
errorOccurred(tr(
"Could Not Read Data - No Data Available!"));
424 buffer.reserve(BUFFER_TRIGGER_SIZE);
427 bool received =
false;
428 while (_socket->hasPendingDatagrams()) {
429 const QNetworkDatagram datagramIn = _socket->receiveDatagram();
430 if (datagramIn.isNull() || datagramIn.data().isEmpty()) {
434 (void) buffer.append(datagramIn.data());
436 if ((buffer.size() > BUFFER_TRIGGER_SIZE) || (timer.elapsed() > RECEIVE_TIME_LIMIT_MS)) {
440 (void) timer.restart();
443 const bool ipLocal = datagramIn.senderAddress().isLoopback() || _localAddresses.contains(datagramIn.senderAddress());
444 const QHostAddress senderAddress = ipLocal ? QHostAddress(QHostAddress::SpecialAddress::LocalHost) : datagramIn.senderAddress();
446 QMutexLocker locker(&_sessionTargetsMutex);
447 if (!containsTarget(_sessionTargets, senderAddress, datagramIn.senderPort())) {
448 qCDebug(UDPLinkLog) <<
"UDP Adding target:" << senderAddress << datagramIn.senderPort();
449 _sessionTargets.append(std::make_shared<UDPClient>(senderAddress, datagramIn.senderPort()));
454 if (!received && buffer.isEmpty()) {
455 qCWarning(UDPLinkLog) <<
"No Data Available to Read!";
462void UDPWorker::_onSocketBytesWritten(qint64 bytes)
464 qCDebug(UDPLinkLog) <<
"Wrote" << bytes <<
"bytes";
467void UDPWorker::_onSocketErrorOccurred(QUdpSocket::SocketError
error)
469 const QString
errorString = _socket->errorString();
470 qCWarning(UDPLinkLog) <<
"UDP Link error:" <<
error << _socket->errorString();
472 if (!_errorEmitted) {
474 _errorEmitted =
true;
484 , _workerThread(new QThread(this))
486 qCDebug(UDPLinkLog) <<
this;
488 _workerThread->setObjectName(QStringLiteral(
"UDP_%1").arg(_udpConfig->
name()));
490 _worker->moveToThread(_workerThread);
493 (void) connect(_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
495 (void) connect(_worker, &
UDPWorker::connected,
this, &UDPLink::_onConnected, Qt::QueuedConnection);
499 (void) connect(_worker, &
UDPWorker::dataSent,
this, &UDPLink::_onDataSent, Qt::QueuedConnection);
501 _workerThread->start();
507 (void) QMetaObject::invokeMethod(_worker,
"disconnectLink", Qt::BlockingQueuedConnection);
511 _workerThread->quit();
512 if (!_workerThread->wait()) {
513 qCWarning(UDPLinkLog) <<
"Failed to wait for UDP Thread to close";
516 qCDebug(UDPLinkLog) <<
this;
526 return QMetaObject::invokeMethod(_worker,
"connectLink", Qt::QueuedConnection);
532 (void) QMetaObject::invokeMethod(_worker,
"disconnectLink", Qt::QueuedConnection);
536void UDPLink::_onConnected()
538 _disconnectedEmitted =
false;
542void UDPLink::_onDisconnected()
544 if (!_disconnectedEmitted.exchange(
true)) {
549void UDPLink::_onErrorOccurred(
const QString &
errorString)
551 qCWarning(UDPLinkLog) <<
"Communication error:" <<
errorString;
555void UDPLink::_onDataReceived(
const QByteArray &data)
560void UDPLink::_onDataSent(
const QByteArray &data)
565void UDPLink::_writeBytes(
const QByteArray& bytes)
567 (void) QMetaObject::invokeMethod(_worker,
"writeData", Qt::QueuedConnection, Q_ARG(QByteArray, bytes));
std::shared_ptr< LinkConfiguration > SharedLinkConfigurationPtr
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Interface holding link specific settings.
virtual void copyFrom(const LinkConfiguration *source)
bool isAutoConnect() const
virtual void setAutoConnect(bool autoc=true)
Set if this is this an Auto Connect configuration.
The link interface defines the interface for all links used to communicate with the ground station ap...
void bytesReceived(LinkInterface *link, const QByteArray &data)
void communicationError(const QString &title, const QString &error)
void bytesSent(LinkInterface *link, const QByteArray &data)
AutoConnectSettings * autoConnectSettings() const
static SettingsManager * instance()
void saveSettings(QSettings &settings, const QString &root) const override
UDPConfiguration(const QString &name, QObject *parent=nullptr)
QList< std::shared_ptr< UDPClient > > targetHosts() const
quint16 localPort() const
Q_INVOKABLE void removeHost(const QString &host)
void setAutoConnect(bool autoc=true) override
Set if this is this an Auto Connect configuration.
void setLocalPort(quint16 port)
Q_INVOKABLE void addHost(const QString &host)
virtual ~UDPConfiguration()
void copyFrom(const LinkConfiguration *source) override
void loadSettings(QSettings &settings, const QString &root) override
bool isSecureConnection() const override
Returns true if the connection is secure (e.g. USB, wired ethernet)
bool isConnected() const override
bool _connect() override
connect is private since all links should be created through LinkManager::createConnectedLink calls
UDPLink(SharedLinkConfigurationPtr &config, QObject *parent=nullptr)
void disconnect() override
void dataReceived(const QByteArray &data)
void dataSent(const QByteArray &data)
void errorOccurred(const QString &errorString)
void writeData(const QByteArray &data)
UDPWorker(const UDPConfiguration *config, QObject *parent=nullptr)
bool isNetworkEthernet()
Check if current network connection is Ethernet.