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

Работа с QUdpSocket в Qt: отправка и приём дейтаграмм

обновлено 2 585 просмотров

Что такое UDP

UDP (User Datagram Protocol) — это лёгкий, ненадёжный, ориентированный на дейтаграммы протокол без установления соединения. Его можно использовать, когда надёжность доставки не важна, а важны скорость и минимальные накладные расходы. В отличие от TCP, UDP не гарантирует ни доставку пакетов, ни их порядок, ни отсутствие дублирования.

Класс QUdpSocket

QUdpSocket — это подкласс QAbstractSocket, который позволяет отправлять и получать дейтаграммы UDP. Наиболее распространённый сценарий использования класса:

  1. Привязка к адресу и порту с помощью метода bind().
  2. Отправка данных через writeDatagram().
  3. Чтение данных через readDatagram() или receiveDatagram().

Ключевые особенности

  • Сокет вызывает сигнал bytesWritten() каждый раз, когда дейтаграмма записывается в сеть.
  • Для отправки дейтаграммы необязательно вызывать bind() — достаточно указать адрес назначения.
  • Сигнал readyRead() вызывается, когда получены дейтаграммы. В этом случае hasPendingDatagrams() возвращает true.
  • Чтобы прочитать первую ожидающую дейтаграмму, нужно:
    • вызвать pendingDatagramSize() — получить размер;
    • вызвать readDatagram() или receiveDatagram() — прочитать её.

Пример использования QUdpSocket

Отправка дейтаграммы

Рассмотрим фрагмент кода для отправки дейтаграммы:

void initSocket()
{
    udpSocket = new QUdpSocket(this);
}

void sendPendingDatagrams(QByteArray &data)
{
    QNetworkDatagram datagram;
    datagram.setDestination(QHostAddress::LocalHost, 8877);
    datagram.setData(data);
    udpSocket->writeDatagram(datagram);
}

Здесь создаётся объект QNetworkDatagram, которому задаётся адрес назначения (LocalHost, порт 8877) и полезная нагрузка (data). Затем дейтаграмма отправляется через writeDatagram().

Приём дейтаграмм

Рассмотрим фрагмент кода для получения дейтаграммы:

void initSocket()
{
    udpSocket = new QUdpSocket(this);
    udpSocket->bind(QHostAddress::LocalHost, 7788);
    connect(udpSocket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams()));
}

void readPendingDatagrams()
{
    while (udpSocket->hasPendingDatagrams()) {
        QNetworkDatagram datagram = udpSocket->receiveDatagram();
        processTheDatagram(datagram);
    }
}

void processTheDatagram(QNetworkDatagram &datagram)
{
    qDebug() << datagram.data();
}

Здесь сокет привязывается к локальному адресу и порту 7788, после чего подключается сигнал readyRead() к слоту readPendingDatagrams(). В слоте в цикле обрабатываются все ожидающие дейтаграммы — это важно, потому что за одно срабатывание readyRead() может прийти сразу несколько пакетов.

Разбор работы примера

  1. initSocket() — создаёт QUdpSocket. В случае приёма дополнительно выполняется bind() на нужный адрес и порт, а также подключается сигнал readyRead().
  2. sendPendingDatagrams() — формирует QNetworkDatagram с адресом назначения и данными, затем отправляет его.
  3. readPendingDatagrams() — вызывается при появлении данных. Пока hasPendingDatagrams() возвращает true, извлекает дейтаграммы через receiveDatagram() и передаёт их на обработку.
  4. processTheDatagram() — выводит содержимое дейтаграммы (в реальном приложении здесь была бы бизнес-логика).

Важные замечания

  1. Исправьте опечатку в коде. В приведённом фрагменте есть ошибка: SIGANL(readyRead()) — должно быть SIGNAL(readyRead()). С этим код просто не скомпилируется.
  2. writeDatagram() возвращает количество записанных байт — стоит проверять возвращаемое значение на -1, что означает ошибку отправки.
  3. Обрабатывайте все ожидающие дейтаграммы в цикле — readyRead() может сработать один раз для нескольких пакетов.
  4. receiveDatagram() vs readDatagram(). receiveDatagram() удобнее — возвращает объект QNetworkDatagram с адресом и портом отправителя, тогда как readDatagram() требует передавать буферы вручную.
  5. UDP не гарантирует доставку. Если нужна надёжность — используйте TCP (QTcpSocket) или реализуйте подтверждения на уровне приложения.
  6. Широковещательная рассылка. Для broadcast используйте QHostAddress::Broadcast, для multicast — joinMulticastGroup().
  7. Современный синтаксис connect() предпочтительнее старого:cppconnect(udpSocket, &QUdpSocket::readyRead, this, &MyClass::readPendingDatagrams);

Итог

QUdpSocket — простой и эффективный инструмент Qt для работы с UDP. Он позволяет отправлять и принимать дейтаграммы с минимальными накладными расходами, что делает его идеальным для задач, где скорость важнее надёжности: потокового видео, онлайн-игр, телеметрии, DNS-подобных запросов. Главное — помнить об отсутствии гарантий доставки и обрабатывать все ожидающие пакеты в цикле.