Что такое UDP
UDP (User Datagram Protocol) — это лёгкий, ненадёжный, ориентированный на дейтаграммы протокол без установления соединения. Его можно использовать, когда надёжность доставки не важна, а важны скорость и минимальные накладные расходы. В отличие от TCP, UDP не гарантирует ни доставку пакетов, ни их порядок, ни отсутствие дублирования.
Класс QUdpSocket
QUdpSocket — это подкласс QAbstractSocket, который позволяет отправлять и получать дейтаграммы UDP. Наиболее распространённый сценарий использования класса:
- Привязка к адресу и порту с помощью метода
bind(). - Отправка данных через
writeDatagram(). - Чтение данных через
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() может прийти сразу несколько пакетов.
Разбор работы примера
initSocket()— создаётQUdpSocket. В случае приёма дополнительно выполняетсяbind()на нужный адрес и порт, а также подключается сигналreadyRead().sendPendingDatagrams()— формируетQNetworkDatagramс адресом назначения и данными, затем отправляет его.readPendingDatagrams()— вызывается при появлении данных. ПокаhasPendingDatagrams()возвращаетtrue, извлекает дейтаграммы черезreceiveDatagram()и передаёт их на обработку.processTheDatagram()— выводит содержимое дейтаграммы (в реальном приложении здесь была бы бизнес-логика).
Важные замечания
- Исправьте опечатку в коде. В приведённом фрагменте есть ошибка:
SIGANL(readyRead())— должно бытьSIGNAL(readyRead()). С этим код просто не скомпилируется. writeDatagram()возвращает количество записанных байт — стоит проверять возвращаемое значение на-1, что означает ошибку отправки.- Обрабатывайте все ожидающие дейтаграммы в цикле —
readyRead()может сработать один раз для нескольких пакетов. receiveDatagram()vsreadDatagram().receiveDatagram()удобнее — возвращает объектQNetworkDatagramс адресом и портом отправителя, тогда какreadDatagram()требует передавать буферы вручную.- UDP не гарантирует доставку. Если нужна надёжность — используйте TCP (
QTcpSocket) или реализуйте подтверждения на уровне приложения. - Широковещательная рассылка. Для broadcast используйте
QHostAddress::Broadcast, для multicast —joinMulticastGroup(). - Современный синтаксис
connect()предпочтительнее старого:cppconnect(udpSocket, &QUdpSocket::readyRead, this, &MyClass::readPendingDatagrams);
Итог
QUdpSocket — простой и эффективный инструмент Qt для работы с UDP. Он позволяет отправлять и принимать дейтаграммы с минимальными накладными расходами, что делает его идеальным для задач, где скорость важнее надёжности: потокового видео, онлайн-игр, телеметрии, DNS-подобных запросов. Главное — помнить об отсутствии гарантий доставки и обрабатывать все ожидающие пакеты в цикле.