1

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

我知道IP_MULTICAST_LOOP套接字选项和类似的问题:
模拟环回接口上的多播,环回设备上的多播,有没有办法在同一个盒子上测试多播 IP?,如何通过 localhost 使用多播来限制流量,可以将来自不同进程的数据多播到同一主机和端口吗?.

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

这是我的研究结果,它似乎可以工作,但我不相信它可以在平台和网络配置的任何其他组合上工作,除了我的电脑有:

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;
  }
//...

抱歉格式错误,这是因为我无法赢得烦人的问题

4

2 回答 2

3

我得出的结论是,从用户的角度来看,它不可能 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”图,如果有任何其他接口已启动,解决方案:禁用所有接口,或修改路由表中的指标
  2. 选择“Teredo Tunneling Pseudo-Interface”,它总是工作(它充当环回接口)
  3. 无论选择什么接口,绑定到任何接口和图表的套接字都将使用该接口的源 ip 传输(即,如果用户选择了环回接口并认为它在本地工作,这是不正确的,图表也会发送到真实网络) ,解决方案与第 1 条中的相同。

在 Windows XP SP3 上的测试显示出令人满意的结果:只有第 3 条(见上文)保留。

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

于 2013-10-25T05:59:50.523 回答
2

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

于 2013-10-23T02:08:35.530 回答