根据最近的经验发现,以及网络上的各种帖子,在启用了个人热点的iPhone上运行的应用程序似乎无法将广播和/或多播发送到个人热点的网络上。有人能阐明这个问题的原因吗?
应用程序
我有一个IOS应用程序,用跨平台C++代码构建,可以在运行的网络上广播和多播它的存在。当iPhone连接到Wi-Fi网络时,该应用程序可以完美工作。在这种情况下,网络上的其他设备接收广播/多播,并且一切正常。这可以通过将运行WireShark的计算机连接到网络来轻松验证——广播/多播数据包可以在数据包跟踪中看到。
不用说,该应用程序在连接到本地Wi-Fi的iPhone上运行良好。
问题
当我在启用了个人热点的iPhone上运行应用程序时,热点网络上不会发布广播/多播。这可以使用WireShark进行验证,WireShark在其跟踪中没有显示这样的数据包。
使用个人热点作为能够处理广播和多播的网络路由器是否有任何限制?
当我使用浏览器在"WireSharking"设备上请求网页时,个人热点会正确响应所有数据包,返回网页内容。
抵押品信息
我遇到过其他Stack Overflow帖子,它们报告了相同或类似的问题:
- 使用iPhone作为热点时TCP连接无法正常工作
- 个人热点发送ssdp广播失败
在iPhone上编写这样的广播/多播应用程序的一个很好的教程是Michael Tyson的《Talkie的制作:多界面广播和多播》。只要说我的应用程序符合所有要求就足够了(例如,在适当的情况下设置套接字选项SO_BROADCAST、SO_DONTROUTE和IP_MULTICAST_IF)。
对上述参考文献(1)的回复写道:"这可能是因为个人热点引入了网络地址翻译吗?"。我过滤了WireShark跟踪,只显示连接到热点IP的数据包,没有证据表明个人热点向NAT地址发送了任何信息。
总结
有人能解释为什么运行个人热点的iPhone不广播/多播数据包,以及如何解决这个问题吗?
非常感谢。
我开始广播了(在iOS 10上)
我发布了第二个答案,因为我找到了一种在iOS 10上以个人热点模式进行广播的方法。解决方案有点复杂,但以下是我如何使其工作的:
- 使用
getifaddrs(if)
循环通过所有接口(do {} while (if = if->ifa_next)
) - 拒绝环回接口(
if->ifa_flags & IFF_LOOPBACK
) - 仅筛选支持广播的接口(
if->ifa_flags & IFF_BROADCAST
) - 使用
int sock = socket()
创建套接字 - 启用广播:
int yes = 1; setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &yes, sizeof yes)
- 连接到广播地址:
connect(sock, if->ifa_dstaddr, if->ifa_dstaddr->sa_len)
- 现在使用
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
快速而肮脏的解决方法
我在开发一款iPhone应用程序时也遇到了同样的问题,该应用程序使用UDP多播消息来发现网络上的设备。显然,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
。。。,CCD_ 16。
因此,我的工作非常简单:我不将广播消息只发送到224.0.0.0
,而是将它们发送到子网中所有其他可能的地址(172.20.10.2
-172.20.10.14
)。
当然,为了在生产应用程序中经得起未来考验,你可能应该检查网络接口列表、IP和子网等,但对于我个人来说,这种方法就足够了。