Работа с сетью в Qt

QUdpSocket: broadcast и multicast рассылка в локальной сети

Введение

UDP — протокол без установления соединения и без гарантии доставки, но он незаменим там, где важна скорость и допустима потеря отдельных пакетов: обнаружение устройств в локальной сети, потоковое видео, игровые серверы, телеметрия. В Qt для работы с UDP используется класс QUdpSocket. В статье разберём, как отправлять широковещательные (broadcast) и групповые (multicast) сообщения — два основных способа рассылки одного пакета сразу нескольким получателям в локальной сети.

Концепция

Broadcast-рассылка отправляется на специальный адрес подсети (например, 255.255.255.255 или адрес-широковещание конкретной подсети) и доставляется всем устройствам в этом сегменте сети. Multicast использует специальный диапазон адресов (224.0.0.0–239.255.255.255) и требует, чтобы получатели явно «подписались» на группу через joinMulticastGroup() — это снижает паразитный трафик по сравнению с broadcast, так как пакеты получают только подписанные узлы (на уровне поддерживающих multicast маршрутизаторов и свитчей).

Для отправки broadcast-пакетов сокет должен явно разрешить эту опцию через setSocketOption(QAbstractSocket::BroadcastSocketOption) в некоторых ОС (или это происходит автоматически в Qt при использовании специальных broadcast-адресов).

Пример кода: QUdpSocket

#include <QCoreApplication>
#include <QUdpSocket>
#include <QNetworkDatagram>
#include <QDebug>
#include <QTimer>

// Отправитель: широковещательное обнаружение устройств
class DiscoverySender : public QObject
{
    Q_OBJECT
public:
    explicit DiscoverySender(QObject *parent = nullptr) : QObject(parent)
    {
        m_timer.setInterval(2000);
        connect(&m_timer, &QTimer::timeout, this, &DiscoverySender::sendBroadcast);
        m_timer.start();
    }

private slots:
    void sendBroadcast()
    {
        QByteArray datagram = "DISCOVER_REQUEST";
        m_socket.writeDatagram(datagram, QHostAddress::Broadcast, 45000);
        qDebug() << "Broadcast-запрос отправлен";
    }

private:
    QUdpSocket m_socket;
    QTimer m_timer;
};

// Получатель: слушает multicast-группу
class MulticastReceiver : public QObject
{
    Q_OBJECT
public:
    explicit MulticastReceiver(QObject *parent = nullptr) : QObject(parent)
    {
        const QHostAddress groupAddress("239.255.43.21");
        m_socket.bind(QHostAddress::AnyIPv4, 45001, QUdpSocket::ShareAddress);
        m_socket.joinMulticastGroup(groupAddress);

        connect(&m_socket, &QUdpSocket::readyRead, this, &MulticastReceiver::onReadyRead);
    }

private slots:
    void onReadyRead()
    {
        while (m_socket.hasPendingDatagrams()) {
            QNetworkDatagram datagram = m_socket.receiveDatagram();
            qDebug() << "Получено от" << datagram.senderAddress().toString()
                     << ":" << datagram.data();
        }
    }

private:
    QUdpSocket m_socket;
};

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    DiscoverySender sender;
    MulticastReceiver receiver;
    return app.exec();
}

#include "main.moc"

Пояснения к коду

DiscoverySender каждые 2 секунды отправляет широковещательный UDP-пакет на адрес QHostAddress::Broadcast и порт 45000 — типичный паттерн для обнаружения устройств в локальной сети без знания их конкретных IP-адресов заранее. MulticastReceiver привязывается к порту с флагом ShareAddress (позволяет нескольким процессам слушать один порт) и присоединяется к multicast-группе 239.255.43.21 через joinMulticastGroup() — после этого сокет начинает получать пакеты, отправленные на этот групповой адрес, даже если они не были отправлены конкретно на IP этого узла.

Использование QNetworkDatagram (вместо более старого readDatagram()) даёт доступ не только к данным, но и к адресу и порту отправителя в одном объекте.

Подводные камни

  • Broadcast не пересекает границы подсети/маршрутизатора. Широковещательные пакеты по умолчанию ограничены локальным сегментом сети — для рассылки между подсетями нужен multicast с настроенной маршрутизацией или явный список адресов получателей.
  • Multicast требует поддержки на сетевом оборудовании. Не все домашние роутеры и корпоративные свитчи корректно обрабатывают IGMP snooping для multicast — в некоторых сетях multicast-трафик может не доходить или, наоборот, рассылаться как broadcast, нагружая всю сеть.
  • UDP не гарантирует ни доставку, ни порядок пакетов. Приложение должно быть готово к потере, дублированию или переупорядочиванию пакетов на уровне протокола — для критичных данных нужен собственный механизм подтверждений (ACK) или использование TCP вместо UDP.
  • Один и тот же порт несколькими процессами. Опция ShareAddress позволяет нескольким процессам слушать один и тот же multicast-порт на одной машине, но без неё попытка повторного bind() на занятый порт завершится ошибкой.