2018-02-08 21:28:33 -05:00
|
|
|
#include "MessageClient.hpp"
|
2024-11-03 23:02:32 -08:00
|
|
|
#include <stdexcept>
|
2018-08-08 17:15:49 -04:00
|
|
|
#include <QApplication>
|
2024-11-02 07:11:46 -07:00
|
|
|
#include <QHostInfo>
|
2024-11-04 20:49:05 -08:00
|
|
|
#include <QNetworkDatagram>
|
2024-11-02 07:11:46 -07:00
|
|
|
#include <QQueue>
|
|
|
|
|
#include <QSet>
|
|
|
|
|
#include <QTimer>
|
|
|
|
|
#include <QUdpSocket>
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2018-09-18 17:24:07 -04:00
|
|
|
#include "DriftingDateTime.h"
|
2018-02-08 21:28:33 -05:00
|
|
|
#include "pimpl_impl.hpp"
|
|
|
|
|
#include "moc_MessageClient.cpp"
|
|
|
|
|
|
2024-11-02 07:11:46 -07:00
|
|
|
/******************************************************************************/
|
|
|
|
|
// Constants
|
|
|
|
|
/******************************************************************************/
|
2018-08-08 17:15:49 -04:00
|
|
|
|
2024-11-02 07:11:46 -07:00
|
|
|
namespace
|
|
|
|
|
{
|
2024-11-02 16:10:10 -07:00
|
|
|
constexpr auto PING_INTERVAL = std::chrono::seconds(15);
|
2024-11-02 07:11:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
// Private Implementation
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
class MessageClient::impl final : public QUdpSocket
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2018-08-08 17:15:49 -04:00
|
|
|
Q_OBJECT
|
2018-02-08 21:28:33 -05:00
|
|
|
|
|
|
|
|
public:
|
2024-11-02 07:11:46 -07:00
|
|
|
|
|
|
|
|
// Constructor
|
|
|
|
|
|
2024-11-02 14:22:22 -07:00
|
|
|
impl(quint16 const port,
|
2024-11-02 09:21:43 -07:00
|
|
|
MessageClient * self)
|
2024-11-02 11:25:49 -07:00
|
|
|
: self_ {self}
|
|
|
|
|
, port_ {port}
|
|
|
|
|
, ping_ {new QTimer {this}}
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-11-02 17:15:31 -07:00
|
|
|
connect(ping_, &QTimer::timeout, this, &impl::ping);
|
|
|
|
|
connect(this, &QIODevice::readyRead, this, [this]()
|
|
|
|
|
{
|
|
|
|
|
while (hasPendingDatagrams())
|
|
|
|
|
{
|
2024-11-04 20:49:05 -08:00
|
|
|
if (auto const datagram = receiveDatagram();
|
|
|
|
|
datagram.isValid())
|
2024-11-02 17:15:31 -07:00
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2024-11-04 20:49:05 -08:00
|
|
|
Q_EMIT self_->message (Message::fromJson(datagram.data()));
|
2024-11-02 17:15:31 -07:00
|
|
|
}
|
|
|
|
|
catch (std::exception const & e)
|
|
|
|
|
{
|
|
|
|
|
Q_EMIT self_->error (QString {"MessageClient exception: %1"}.arg(e.what()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-11-02 16:10:10 -07:00
|
|
|
ping_->start(PING_INTERVAL);
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-11-02 07:11:46 -07:00
|
|
|
bind();
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2024-11-02 07:11:46 -07:00
|
|
|
// Destructor
|
|
|
|
|
|
2024-11-02 14:22:22 -07:00
|
|
|
~impl()
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-11-03 11:22:57 -08:00
|
|
|
abort_host_lookup();
|
|
|
|
|
|
2024-11-02 11:25:49 -07:00
|
|
|
if (port_ && !host_.isNull())
|
2024-11-02 07:11:46 -07:00
|
|
|
{
|
2024-11-03 11:22:57 -08:00
|
|
|
send_message({"CLOSE"});
|
2024-11-02 07:11:46 -07:00
|
|
|
}
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2024-11-02 11:25:49 -07:00
|
|
|
// Send a ping message, if we have a valid port and host.
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-11-02 07:11:46 -07:00
|
|
|
void
|
2024-11-02 11:25:49 -07:00
|
|
|
ping()
|
2024-11-02 07:11:46 -07:00
|
|
|
{
|
2024-11-02 11:25:49 -07:00
|
|
|
if (port_ && !host_.isNull())
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-11-03 11:22:57 -08:00
|
|
|
send_message({"PING", "", {
|
2024-11-02 11:25:49 -07:00
|
|
|
{"NAME", QVariant(QApplication::applicationName())},
|
|
|
|
|
{"VERSION", QVariant(QApplication::applicationVersion())},
|
|
|
|
|
{"UTC", QVariant(DriftingDateTime::currentDateTimeUtc().toMSecsSinceEpoch())}
|
2024-11-03 11:22:57 -08:00
|
|
|
}});
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
2024-11-02 07:11:46 -07:00
|
|
|
}
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-11-03 11:22:57 -08:00
|
|
|
// If the JSON-serialized form of the message isn't exactly the same as
|
|
|
|
|
// the one that we last sent, send it and note it as the prior datagram
|
|
|
|
|
// sent.
|
2024-11-02 11:25:49 -07:00
|
|
|
//
|
|
|
|
|
// Caller is required to make the determination that our port and host
|
|
|
|
|
// are valid prior to calling this function.
|
|
|
|
|
|
2024-11-02 07:11:46 -07:00
|
|
|
void
|
2024-11-03 11:22:57 -08:00
|
|
|
send_message(Message const & message)
|
2024-11-02 07:11:46 -07:00
|
|
|
{
|
2024-11-03 11:22:57 -08:00
|
|
|
if (auto const datagram = message.toJson();
|
|
|
|
|
datagram != lastDatagram_)
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-11-03 11:22:57 -08:00
|
|
|
writeDatagram(datagram, host_, port_);
|
|
|
|
|
lastDatagram_ = datagram;
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
2024-11-02 07:11:46 -07:00
|
|
|
}
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-11-03 08:19:39 -08:00
|
|
|
// If we've got a host lookup in flight, but not yet completed, abort it
|
|
|
|
|
// and indicate that we no longer have one in flight.
|
2024-11-03 07:48:53 -08:00
|
|
|
|
|
|
|
|
void
|
2024-11-03 08:11:34 -08:00
|
|
|
abort_host_lookup()
|
2024-11-03 07:48:53 -08:00
|
|
|
{
|
|
|
|
|
if (hostLookupId_ != -1)
|
|
|
|
|
{
|
|
|
|
|
QHostInfo::abortHostLookup(hostLookupId_);
|
|
|
|
|
hostLookupId_ = -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Abort any current host lookup that might be in flight, and start a new
|
|
|
|
|
// host lookup for the provided server name, noting that we have a lookup
|
|
|
|
|
// in flight.
|
|
|
|
|
//
|
|
|
|
|
// If, at the time of host lookup completion, we find ourselves to be the
|
|
|
|
|
// active host lookup, and we were able to look up addresses, then use the
|
|
|
|
|
// first address associated with the server as our host address, and send
|
|
|
|
|
// a ping.
|
2024-11-02 11:31:10 -07:00
|
|
|
//
|
|
|
|
|
// No matter the result of the host lookup, we're going to drain the queue,
|
|
|
|
|
// either via sending messages if the host lookup worked, or by clearing it
|
2024-11-02 14:40:46 -07:00
|
|
|
// if the lookup failed.
|
2024-11-02 07:11:46 -07:00
|
|
|
|
|
|
|
|
void
|
2024-11-03 08:11:34 -08:00
|
|
|
queue_host_lookup(QString const & name)
|
2024-11-02 07:11:46 -07:00
|
|
|
{
|
2024-11-03 08:11:34 -08:00
|
|
|
abort_host_lookup();
|
2024-11-03 07:48:53 -08:00
|
|
|
|
2024-11-03 08:11:34 -08:00
|
|
|
hostLookupId_ = QHostInfo::lookupHost(name,
|
2024-11-02 11:25:49 -07:00
|
|
|
this,
|
|
|
|
|
[this](QHostInfo const & info)
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-11-03 08:19:39 -08:00
|
|
|
// This functor is always called in the context of the thread that
|
|
|
|
|
// made the call to lookupHost(), so we're safe to modify anything
|
|
|
|
|
// that we were safe to modify outside.
|
|
|
|
|
|
2024-11-02 11:25:49 -07:00
|
|
|
if (info.lookupId() == hostLookupId_)
|
2024-11-02 07:11:46 -07:00
|
|
|
{
|
2024-11-02 11:25:49 -07:00
|
|
|
hostLookupId_ = -1;
|
2024-11-02 07:11:46 -07:00
|
|
|
|
2024-11-02 11:25:49 -07:00
|
|
|
if (auto const & list = info.addresses();
|
|
|
|
|
!list.isEmpty())
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-11-03 06:58:08 -08:00
|
|
|
host_ = list.first();
|
2024-11-02 11:25:49 -07:00
|
|
|
|
2024-11-03 07:48:53 -08:00
|
|
|
qDebug() << "MessageClient Host:" << host_.toString()
|
|
|
|
|
<< "loopback:" << host_.isLoopback()
|
|
|
|
|
<< "multicast:" << host_.isMulticast();
|
|
|
|
|
|
2024-11-02 14:40:46 -07:00
|
|
|
ping();
|
2024-11-02 07:11:46 -07:00
|
|
|
|
2024-11-03 10:42:29 -08:00
|
|
|
if (port_ && !host_.isNull())
|
2024-11-02 07:11:46 -07:00
|
|
|
{
|
2024-11-03 11:22:57 -08:00
|
|
|
while (!messageQueue_.isEmpty()) send_message(messageQueue_.dequeue());
|
2024-11-02 07:11:46 -07:00
|
|
|
}
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
2024-11-02 07:11:46 -07:00
|
|
|
else
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-11-02 11:25:49 -07:00
|
|
|
Q_EMIT self_->error (QString {"UDP server lookup failed: %1"}.arg(info.errorString()));
|
|
|
|
|
messageQueue_.clear();
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
2024-11-02 07:11:46 -07:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-11-02 07:11:46 -07:00
|
|
|
// Data members
|
|
|
|
|
|
2024-11-03 11:22:57 -08:00
|
|
|
MessageClient * self_;
|
|
|
|
|
quint16 port_;
|
|
|
|
|
QTimer * ping_;
|
|
|
|
|
QHostAddress host_;
|
|
|
|
|
int hostLookupId_ = -1;
|
|
|
|
|
QQueue<Message> messageQueue_;
|
|
|
|
|
QByteArray lastDatagram_;
|
2024-11-02 07:11:46 -07:00
|
|
|
};
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-11-02 07:11:46 -07:00
|
|
|
/******************************************************************************/
|
|
|
|
|
// Implementation
|
|
|
|
|
/******************************************************************************/
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-11-02 07:11:46 -07:00
|
|
|
#include "MessageClient.moc"
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-11-03 11:22:57 -08:00
|
|
|
// Constructor
|
|
|
|
|
//
|
|
|
|
|
// On Windows, Qt will seemingly emit spurious 'connection refused' errors
|
|
|
|
|
// for UDP sockets; ignore them if they appear.
|
|
|
|
|
|
2024-11-03 08:11:34 -08:00
|
|
|
MessageClient::MessageClient(QString const & name,
|
2024-11-02 14:22:22 -07:00
|
|
|
quint16 const port,
|
|
|
|
|
QObject * parent)
|
|
|
|
|
: QObject {parent}
|
|
|
|
|
, m_ {port, this}
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-11-02 11:25:49 -07:00
|
|
|
connect(&*m_, &impl::errorOccurred, [this](impl::SocketError e)
|
2024-11-02 07:11:46 -07:00
|
|
|
{
|
2024-11-03 11:22:57 -08:00
|
|
|
#if defined (Q_OS_WIN)
|
2024-11-02 11:25:49 -07:00
|
|
|
if (e != impl::NetworkError &&
|
|
|
|
|
e != impl::ConnectionRefusedError)
|
2018-02-08 21:28:33 -05:00
|
|
|
#else
|
2024-11-02 07:11:46 -07:00
|
|
|
Q_UNUSED (e);
|
2018-02-08 21:28:33 -05:00
|
|
|
#endif
|
2024-11-02 07:11:46 -07:00
|
|
|
{
|
|
|
|
|
Q_EMIT error (m_->errorString());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2024-11-03 08:11:34 -08:00
|
|
|
set_server_name(name);
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2024-11-03 11:22:57 -08:00
|
|
|
// Host accessor; returns host information for the server. Note
|
|
|
|
|
// that this will return an invalid object while a host lookup
|
|
|
|
|
// is in flight, so consider thread inertia before relying on
|
|
|
|
|
// this; the case may be that information is coming, but it's
|
|
|
|
|
// not here just yet.
|
|
|
|
|
|
2024-11-02 07:11:46 -07:00
|
|
|
QHostAddress
|
2024-11-03 08:11:34 -08:00
|
|
|
MessageClient::server_host() const
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-11-02 11:25:49 -07:00
|
|
|
return m_->host_;
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2024-11-03 11:22:57 -08:00
|
|
|
// Port accessor; if zero, anything sent to us will be dropped
|
|
|
|
|
// on the floor.
|
|
|
|
|
|
2024-11-02 14:22:22 -07:00
|
|
|
quint16
|
2024-11-02 07:11:46 -07:00
|
|
|
MessageClient::server_port() const
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-11-02 11:25:49 -07:00
|
|
|
return m_->port_;
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2024-11-03 11:22:57 -08:00
|
|
|
// Set our server name. If the name is empty, then what happens to
|
|
|
|
|
// messages provided to us via send() depends on what our port is
|
|
|
|
|
// set to. If it's zero, they'll just be dropped on the floor. If
|
|
|
|
|
// it's non-zero, they'll be queued until this method is called
|
|
|
|
|
// again with a non-empty name.
|
|
|
|
|
|
2024-11-02 07:11:46 -07:00
|
|
|
void
|
2024-11-03 08:11:34 -08:00
|
|
|
MessageClient::set_server_name(QString const & name)
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-11-02 11:25:49 -07:00
|
|
|
m_->host_.clear();
|
2024-11-02 07:11:46 -07:00
|
|
|
|
2024-11-03 10:29:27 -08:00
|
|
|
if (name.isEmpty()) m_->abort_host_lookup();
|
|
|
|
|
else m_->queue_host_lookup(name);
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2024-11-03 11:22:57 -08:00
|
|
|
// Set our port; if zero, no sending will be performed, and anything
|
|
|
|
|
// sent us for disposition via send() will be dropped on the floor.
|
|
|
|
|
|
2024-11-02 07:11:46 -07:00
|
|
|
void
|
2024-11-02 14:22:22 -07:00
|
|
|
MessageClient::set_server_port(quint16 const port)
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-11-02 11:25:49 -07:00
|
|
|
m_->port_ = port;
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2024-11-03 11:22:57 -08:00
|
|
|
// If we've got a port, i.e., we're supposed to send messages, then queue
|
|
|
|
|
// the message for later transmission if we don't have a host yet; attempt
|
|
|
|
|
// to send it immediately if we've got a host.
|
|
|
|
|
|
2024-11-02 07:11:46 -07:00
|
|
|
void
|
|
|
|
|
MessageClient::send(Message const & message)
|
|
|
|
|
{
|
2024-11-03 11:22:57 -08:00
|
|
|
if (m_->port_)
|
|
|
|
|
{
|
|
|
|
|
if (m_->host_.isNull()) m_->messageQueue_.enqueue(message);
|
|
|
|
|
else m_->send_message(message);
|
|
|
|
|
}
|
2018-08-07 22:41:01 -04:00
|
|
|
}
|
|
|
|
|
|
2024-11-02 07:11:46 -07:00
|
|
|
/******************************************************************************/
|