QUdpSocket: Как заставить многоадресную рассылку работать также на локальном хосте, но предотвратить петлю на приложение?
Мое приложение Qt использует многоадресную рассылку QUdpSocket
и требуется полудуплексный режим (он имитирует радиопередачу между симплексными радиостанциями). Это означает, что один экземпляр приложения не должен получать дейтаграммы, которые он отправляет. Но он также должен поддерживать работу нескольких экземпляров на одном компьютере (пользователь явно выбирает интерфейс обратной связи). И, конечно, он должен быть переносимым (в худшем случае, Windows и Linux).
Я в курсе IP_MULTICAST_LOOP
опция сокета и похожие вопросы:
Имитация многоадресной рассылки на петлевом интерфейсе, Многоадресная рассылка на петлевом устройстве. Есть ли способ проверить многоадресную рассылку IP на той же самой коробке? Как ограничить трафик с помощью многоадресной рассылки по локальному узлу, Можно ли многоадресно передавать данные из разных процессов на один и тот же хост и порт?,
Обсуждения почти близки, чтобы ответить на мой вопрос, но до сих пор неясно (в основном потому, что мне кажется, что поведение на разных платформах различно).
Так как мне установить сокет? Если это невозможно сделать с помощью простой конфигурации соединения, то может быть гарантировано использование connectToHost() с ReadOnly/WriteOnly?
Обновить:
Вот результат моего исследования, который SEEMS работает, но я не верю, что он будет работать на любой другой комбинации конфигурации платформы и сети, кроме моего компьютера:
void initNetwork() {
//...
/* It will be needed to filter out own loopbacked datagrams */
local_addresses = QNetworkInterface::allAddresses();
/* Interface, selected by user */
QNetworkInterface multicast_netif = <user selected>;
Q_ASSERT(multicast_netif.isValid());
/* Yes, I already accept the fact, that I need two separate sockets (there are more chances to make it work than when using bidirectional one) */
udpSocketIn = new QUdpSocket(this);
udpSocketOut = new QUdpSocket(this);
/* It's important to bind to Any. No other combinations work (including LocalHost (in case if user selected loopback interface), MULTICAST_ADDR) */
result = udpSocketIn->bind(QHostAddress::Any, MULTICAST_PORT, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
Q_ASSERT(result);
/* It required to only make application know real(!) udpSocketOut->localPort() in order to be able filter own datagrams */
result = udpSocketOut->bind();
Q_ASSERT(result);
/* One of rare things, I'm sure is correct and must be done */
result = udpSocketIn->joinMulticastGroup(QHostAddress(MULTICAST_ADDR), multicast_netif);
Q_ASSERT(result);
/* It doesn't matter, but it will fail if socket not binded */
//result = udpSocketOut->joinMulticastGroup(QHostAddress(MULTICAST_ADDR), multicast_netif);
//Q_ASSERT(result);
/* No, you can't ! If socket binded previously and loopback interface selected, datagrams will not be transfered. I don't know why. And this is major thing, which makes me think, that this configuration isn't reliable, because stupid windows will select default interface for outgoing datagrams ! */
//udpSocketOut->setMulticastInterface(multicast_netif);
/* It doesn't matter, because it set by default. */
//udpSocketIn->setSocketOption(QAbstractSocket::MulticastLoopbackOption, QVariant(1));
//udpSocketOut->setSocketOption(QAbstractSocket::MulticastLoopbackOption, QVariant(1));
//...
}
void sendDatagram() {
//...
/* It almost always return ok, regardless of datagram being sent actually or not.
One exception is when I turn off real network interface to which it was binded by udpSocketOut->bind() call (it selected by OS, although user selected loopback interface !)
*/
result = udpSocketOut->writeDatagram(datagram, QHostAddress((MULTICAST_ADDR), MULTICAST_PORT);
Q_ASSERT(result == datagram.size());
//...
}
void readPendingDatagrams() {
//...
udpSocketIn->readDatagram(datagram, &senderHost, &senderPort);
/* Thanks to udpSocketOut->bind() we are able to filter out own packets sent from udpSocketOut */
if ((local_addresses.contains(senderHost)) && (senderPort == udpSocketOut->localPort())) {
// Ignore loopbacked datagram
return;
}
//...
Извините за плохое форматирование, это потому что я не смог победить надоедливую проблему
2 ответа
Я пришел к выводу, что из-за специфических реализаций сетей ОС невозможно заставить его работать на 100% корректно, как ожидалось.
Следующий код обеспечивает решение:
void initNetwork() {
//...
/* It will be needed to filter out own loopbacked datagrams */
local_addresses = QNetworkInterface::allAddresses();
/* Interface, selected by user */
QNetworkInterface multicast_netif = <user selected>;
/* Two separate sockets for receiving and sending (allows differentiate source port from destination port) */
udpSocketIn = new QUdpSocket(this);
udpSocketOut = new QUdpSocket(this);
/* It's important to bind to Any for multicast to work, also port must be reusable by all application instances on same host */
udpSocketIn->bind(QHostAddress::Any, MULTICAST_PORT, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
/* It required to only make application know real(!) udpSocketOut->localPort() in order to be able filter own datagrams */
udpSocketOut->bind();
/* Obvious... */
udpSocketIn->joinMulticastGroup(QHostAddress(MULTICAST_ADDR), multicast_netif);
udpSocketOut->setMulticastInterface(multicast_netif);
/* Multicast loopback is set by default, but set it explicitly just in case. */
udpSocketIn->setSocketOption(QAbstractSocket::MulticastLoopbackOption, QVariant(1));
udpSocketOut->setSocketOption(QAbstractSocket::MulticastLoopbackOption, QVariant(1));
//...
}
void sendDatagram() {
//...
udpSocketOut->writeDatagram(datagram, QHostAddress((MULTICAST_ADDR), MULTICAST_PORT);
//...
}
void readPendingDatagrams() {
//...
udpSocketIn->readDatagram(datagram, &senderHost, &senderPort);
/* Thanks to udpSocketOut->bind() we are able to filter out own packets sent from udpSocketOut */
if ((local_addresses.contains(senderHost)) && (senderPort == udpSocketOut->localPort())) {
// ignore loopbacked datagram
} else {
// accept diagram
}
//...
Тесты на Linux (только с двумя интерфейсами: lo
а также eth0
) показал отличные результаты. Я выбираю нужный интерфейс, и он работает на 99% правильно, как и ожидалось. Было бы 100%, если бы это не была маленькая ошибка lo
первая датаграмма интерфейса отправляется (или принимается) с исходным IP-адресом eth0
интерфейс.
Тесты на 64-битной Windows 7 (со многими различными интерфейсами) показали, что в некоторых случаях пользователю приходится играть с конфигурацией сети системы, чтобы она работала. Вот некоторые наблюдения:
- с выбранными диаграммами "Loopback Pseudo-Interface 1" не переданными, если есть какой-либо другой интерфейс, решение: либо отключите все интерфейсы, либо измените метрики в таблице маршрутов
- с выбранным "Псевдоинтерфейсом туннелирования Teredo" он работает всегда (он действует как петлевой интерфейс)
- независимо от выбранного интерфейса, сокет (ы), связанный с любым интерфейсом, и диаграммы будут переданы с исходным ip этого интерфейса (т. е. если пользователь выбрал петлевой интерфейс и считает, что он работает локально, это не так, диаграммы выходят и в реальную сеть) Решение будет таким же, как в пункте 1.
Тесты на Windows XP SP3 показали удовлетворительные результаты: сохраняется только пункт 3 (см. Выше).
Надеюсь, что мое исследование будет полезно для людей, испытывающих подобные проблемы.
Многоадресная обратная связь означает, что многоадресные рассылки, отправленные с хоста, также принимаются всеми членами группы, выполняющими на хосте. Он включен для каждого сокета с точки зрения отправителя, но он применяется ко всему хосту и всем приложениям, работающим в нем, с точки зрения получения.