QUdpSocket:如何在localhost上使多播工作,但是防止每个应用程序的环回?

时间:2013-10-22 10:18:37

标签: qt qtnetwork qudpsocket

我的Qt应用程序使用多播QUdpSocket并需要半双工操作(它模拟单工无线电之间的无线电传输)。这意味着一个应用程序实例不得接收它发送的数据报。但它也必须支持在同一台机器上运行多个实例(用户明确选择环回接口)。当然,它应该是可移植的(最坏的情况是Windows和Linux)。

我知道IP_MULTICAST_LOOP套接字选项和类似的问题:
Simulating multicasting on loopback interfaceMulticasting on loopback deviceIs there a way to test multicast IP on same box?How to limit traffic using multicast over localhostIs it okay to multicast data from different processes to the same host and port?

讨论几乎接近我的答案,但目前还不清楚(主要是因为在我看来,不同平台上的行为有所不同)。 那么我应该如何设置套接字?如果通过简单的连接配置无法实现它,那么使用带有ReadOnly / WriteOnly的connectToHost()将有助于保证?
更新:

以下是我的研究结果,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;
  }
//...
  

抱歉格式不正确,这是因为我无法赢得恼人的problem

2 个答案:

答案 0 :(得分:2)

组播环回意味着主机发送的组播也会被主机中执行的任何组成员接收。从发送者的角度来看,它是按套接字启用的,但从接收的角度来看,它适用于整个主机及其中运行的所有应用程序。

答案 1 :(得分: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上的测试(仅限两个界面:loeth0)显示了完美的结果。我选择了所需的界面,它按预期正常工作99%。如果它不是小错误,那将是100%:在绑定的lo接口上,使用eth0接口的源IP发送(或接收)第一个数据报。

Windows 7 64位测试(具有许多不同的接口)显示,在某些情况下,用户必须使用系统网络配置才能使其正常工作。以下是一些观察结果:

  1. 选择的“Loopback Pseudo-Interface 1”图未传输,如果有任何其他接口启动,解决方案:要么禁用所有接口,要么modify metrics in route table
  2. 选择“Teredo Tunneling Pseudo-Interface”它始终有效(它充当环回接口)
  3. 无论选择哪个接口,绑定到任何接口和图表的套接字都将使用该接口的源IP进行传输(即,如果用户选择了环回接口,并认为它在本地工作,则不是真的,图表是真实的网络也),解决方案与第1条相同。
  4. Windows XP SP3上的测试显示了令人满意的结果:只有第3条(见上文)保留。

    希望我的研究对遇到类似问题的人有用。