7

根据最近的实证研究结果和网络上的各种帖子,在启用了个人热点的 iPhone 上运行的应用程序似乎无法将广播和/或多播发送到个人热点的网络上。任何人都可以阐明这个问题的原因吗?

应用程序

我有一个 IOS 应用程序,使用跨平台 C++ 代码构建,它可以将其存在广播并多播到它正在运行的网络上。当 iPhone 连接到 Wi-Fi 网络时,该应用程序可以完美运行。在这种情况下,网络上的其他设备接收广播/多播,并且一切正常。这可以通过将运行 WireShark 的计算机连接到网络来轻松验证——广播/多播数据包可以在数据包跟踪中看到。

不用说,该应用程序在连接到本地 Wi-Fi 的 iPhone 上运行良好。

问题

当我在启用了个人热点的 iPhone 上运行该应用程序时,不会向热点网络发布任何广播/多播。这可以使用 WireShark 进行验证,它在其跟踪中显示没有此类数据包。

将个人热点用作能够处理广播和多播的网络路由器是否有任何限制?

当我使用浏览器在“WireSharking”设备上请求网页时,个人热点正确响应所有数据包,返回网页内容。

抵押信息

我遇到过报告相同或类似问题的其他 Stack Overflow 帖子:

  1. 使用 iPhone 作为热点时 TCP 连接无法正常工作
  2. 个人热点发送ssdp广播失败

在 iPhone 上编写这种广播/多播应用程序的一个很好的教程是 Michael Tyson 的“ Talkie 的制作:多接口广播和多播”。只要说我的应用程序符合所有要求就足够了(例如,在适当的地方设置套接字选项 SO_BROADCAST、SO_DONTROUTE 和 IP_MULTICAST_IF)。

上面对参考(1)的回复写道“会不会是因为个人热点引入了网络地址转换? ”。我过滤了 WireShark 跟踪以仅显示连接到热点 IP 的数据包,并且没有证据表明个人热点向 NAT 地址发送任何内容。

总之

谁能解释为什么运行个人热点的 iPhone 不广播/多播数据包,以及如何解决这个问题?

提前谢谢了。

4

2 回答 2

7

我得到了广播工作(在 iOS 10 上)

我发布了第二个答案,因为我找到了一种在个人热点模式下在 iOS 10 上进行广播的方法。解决方案有点复杂,但这是我如何让它工作的:

  1. 用于getifaddrs(if)循环遍历所有接口 ( do {} while (if = if->ifa_next))
  2. 拒绝环回接口 ( if->ifa_flags & IFF_LOOPBACK)
  3. 只过滤支持广播的接口 ( if->ifa_flags & IFF_BROADCAST)
  4. 创建一个套接字int sock = socket()
  5. 启用广播:int yes = 1; setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &yes, sizeof yes)
  6. 连接到广播地址:connect(sock, if->ifa_dstaddr, if->ifa_dstaddr->sa_len)
  7. 现在使用send()发送您的消息!

我已经确认这在 iPhone 连接到 WiFi 网络时以及在个人热点模式下都有效。

完整示例代码

#include <ifaddrs.h>
#include <arpa/inet.h>
#include <net/if.h>

-(IBAction)sayHello:(id)sender {

    // fetch a linked list of all network interfaces
    struct ifaddrs *interfaces;
    if (getifaddrs(&interfaces) == -1) {
        NSLog(@"getifaddrs() failed: %s", strerror(errno));
        return;
    }

    // loop through the linked list
    for(struct ifaddrs *interface=interfaces; interface; interface=interface->ifa_next) {

        // ignore loopback interfaces
        if (interface->ifa_flags & IFF_LOOPBACK) continue;

        // ignore interfaces that don't have a broadcast address
        if (!(interface->ifa_flags & IFF_BROADCAST) || interface->ifa_dstaddr == NULL) continue;

        // check the type of the address (IPv4, IPv6)
        int protocol_family;
        struct sockaddr_in ipv4_addr = {0};
        struct sockaddr_in6 ipv6_addr = {0};
        struct sockaddr *addr;
        if (interface->ifa_dstaddr->sa_family == AF_INET) {
            if (interface->ifa_dstaddr->sa_len > sizeof ipv4_addr) {
                NSLog(@"Address too big");
                continue;
            }
            protocol_family = PF_INET;
            memcpy(&ipv4_addr, interface->ifa_dstaddr, interface->ifa_dstaddr->sa_len);
            ipv4_addr.sin_port = htons(16000);
            addr = (struct sockaddr *)&ipv4_addr;

            char text_addr[255] = {0};
            inet_ntop(AF_INET, &ipv4_addr.sin_addr, text_addr, sizeof text_addr);
            NSLog(@"Sending message to %s:%d", text_addr, ntohs(ipv4_addr.sin_port));
        }
        else if (interface->ifa_dstaddr->sa_family == AF_INET6) {
            if (interface->ifa_dstaddr->sa_len > sizeof ipv6_addr) {
                NSLog(@"Address too big");
                continue;
            }
            protocol_family = PF_INET6;
            memcpy(&ipv6_addr, interface->ifa_dstaddr, interface->ifa_dstaddr->sa_len);
            ipv6_addr.sin6_port = htons(16000);
            addr = (struct sockaddr *)&ipv6_addr;

            char text_addr[255] = {0};
            inet_ntop(AF_INET6, &ipv6_addr.sin6_addr, text_addr, sizeof text_addr);
            NSLog(@"Sending message to %s:%d", text_addr, ntohs(ipv6_addr.sin6_port));
        }
        else {
            NSLog(@"Unsupported protocol: %d", interface->ifa_dstaddr->sa_family);
            continue;
        }

        // create a socket
        int sock = socket(protocol_family, SOCK_DGRAM, IPPROTO_UDP);
        if (sock == -1) {
            NSLog(@"socket() failed: %s", strerror(errno));
            continue;
        }

        // configure the socket for broadcast mode
        int yes = 1;
        if (-1 == setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &yes, sizeof yes)) {
            NSLog(@"setsockopt() failed: %s", strerror(errno));
        }

        // create some bytes to send
        NSString *message = @"Hello world!\n";
        NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];

        if (-1 == connect(sock, addr, addr->sa_len)) {
            NSLog(@"connect() failed: %s", strerror(errno));
        }

        // send the message
        ssize_t sent_bytes = send(sock, data.bytes, data.length, 0);
        if (sent_bytes == -1) {
            NSLog(@"send() failed: %s", strerror(errno));
        }
        else if (sent_bytes<data.length) {
            NSLog(@"send() sent only %d of %d bytes", (int)sent_bytes, (int)data.length);
        }

        // close the socket! important! there is only a finite number of file descriptors available!
        if (-1 == close(sock)) {
            NSLog(@"close() failed: %s", strerror(errno));
        }
    }

    freeifaddrs(interfaces);
}

此示例方法在端口 16000 上广播 UDP 消息。

对于调试,您可以使用该工具socat。只需在您的计算机上运行以下命令(应该与手机在同一网络上):

socat UDP-RECV:16000 STDOUT
于 2016-11-10T09:53:32.250 回答
3

快速而肮脏的解决方法

在开发使用 UDP 多播消息来发现网络上的设备的 iPhone 应用程序时,我也遇到了同样的问题。显然,iPhone 在个人热点模式下阻止了多播消息。

但是,iPhone 似乎将172.20.10.0/28子网用于个人热点网络上的设备。这意味着只有 16 个可能的地址。其中,172.20.10.0显然没有使用,172.20.10.1是 iPhone 本身,发送消息172.20.10.15失败并出现“不允许”错误。这意味着客户端只能使用以下 13 个地址: 172.20.10.2, 172.20.10.3, ..., 172.20.10.14

所以我的解决方法非常简单:224.0.0.0我不仅将广播消息发送到 to ,还将它们发送到子网中的所有其他可能地址(172.20.10.2- 172.20.10.14)。

当然,为了在生产应用程序中面向未来,您可能应该检查网络接口列表、检查 IP 和子网等,但对于我个人而言,这种方法就足够了。

于 2016-11-10T08:06:55.540 回答