networking广播/组播在个人热点模式下不由iPhone发送

根据最近的实证研究结果,并基于networking上的各种post,似乎运行在具有个人热点的iPhone上的应用程序不能将广播和/或多播发送到个人热点networking上。 任何人都可以阐明这个问题的原因吗?

应用程序

我有一个跨平台的C ++代码构build的IOS应用程序,它可以在networking上广播和多播它的存在。 当iPhone连接到Wi-Finetworking时,该应用程序完美无瑕。 在这种情况下,networking上的其他设备接收广播/多播,并且一切正常。 通过将一台运行WireShark的计算机连接到networking,可以轻松validation这一点 – 广播/多播数据包可以在数据包追踪中看到。

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

问题

当我在启用了个人热点的iPhone上运行应用程序时,不会将广播/多播发布到热点networking上。 这可以使用WireShark进行validation,WireShark在其跟踪中没有显示这样的数据包。

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

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

抵押信息

我遇到了其他堆栈溢出过去报告相同或类似的问题:

  1. 使用iPhone作为热点时,TCP连接无法正常工作
  2. 未能通过个人热点发送ssdp广播

Michael Tyson的“ The Making of Talkie:Multi-interface broadcast and multicast ”是一本在iPhone上编写这种广播/多播应用的好教程。 只需说我的应用程序符合所有要求(例如,在适当的情况下设置套接字选项SO_BROADCAST,SO_DONTROUTE和IP_MULTICAST_IF)即可。

上面引用(1)的回复写道:“ 可能是因为个人热点引入了networking地址转换? ”。 我过滤了WireShark跟踪,只显示连接到热点IP的数据包,没有个人热点向NAT地址发送任何东西的证据。

综上所述

任何人都可以解释为什么运行个人热点的iPhone不会广播/组播数据包,以及如何解决这个问题?

提前谢谢了。

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

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

  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()创build一个套接字
  5. 启用广播: int yes = 1; setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &yes, sizeof yes) 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连接到WiFinetworking时,以及在个人热点模式下,这都是有效的。

完整的示例代码

 #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消息。

为了debugging,你可以使用工具socat 。 只需在计算机上运行以下命令(应该与电话在同一networking上):

 socat UDP-RECV:16000 STDOUT 

快速和肮脏的解决方法

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

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

所以我的解决方法非常简单:我不是只发送广播消息到224.0.0.0 ,而是将它们发送到子网( 172.20.10.2172.20.10.14 )中的所有其他可能的地址。

当然,为了在生产应用程序中保持未来,您应该检查networking接口列表,检查IP和子网等,但对于我个人使用这种方法已经足够了。