为什么无法接收(UDP组播)数据包?

3

所以,我一直在尝试弄清楚为什么这个不起作用,但我一点头绪也没有。我已经成功地从 iPhone 发送数据包并在我的 Mac 上接收到它们。根据 tcpdump 的记录,我的 Mac 正确地发送了数据包。此外,如果我在模拟器中运行它,它也可以正常工作。这让我相信这是一个网络问题,但我不知道可能是什么,所以我希望(!)它是在下面的某个地方出了问题。

CFSocketContext socketContext = {0, self, NULL, NULL, NULL};
advertiseSocket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_DGRAM, IPPROTO_UDP, kCFSocketDataCallBack, (CFSocketCallBack)&advertiseCallBack, &socketContext);

int yes = 1;
setsockopt(CFSocketGetNative(advertiseSocket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));

u_char loop = 0;
setsockopt(CFSocketGetNative(advertiseSocket), IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)); 

unsigned char ttl = 64;
setsockopt(CFSocketGetNative(advertiseSocket), IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));        

struct sockaddr_in addressData;
memset(&addressData, 0, sizeof(addressData));
addressData.sin_len = sizeof(addressData);
addressData.sin_family = AF_INET;
addressData.sin_port = htons(broadcastPort);
addressData.sin_addr.s_addr = htonl(INADDR_ANY);
NSData *address = [NSData dataWithBytes:&addressData length:sizeof(addressData)];
CFSocketSetAddress(advertiseSocket, (CFDataRef)address);

struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr(broadcastIP);         
mreq.imr_interface.s_addr = INADDR_ANY;

setsockopt(CFSocketGetNative(advertiseSocket), IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));

// set up the run loop sources for the sockets
CFRunLoopRef cfrl = CFRunLoopGetCurrent();
CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, advertiseSocket, 0);
CFRunLoopAddSource(cfrl, source, kCFRunLoopCommonModes);
CFRelease(source);

编辑:

上述代码是iPhone接收端的代码。

我正在使用下面的Java代码与iPhone进行通讯(这是压缩过的)。发送的数据包未被iPhone接收,但Mac却接收了iPhone发送的数据包。

String ident = broadcastKey;
MulticastSocket socket = new MulticastSocket(broadcastPort);
InetAddress group = InetAddress.getByName(broadcastIP);
socket.joinGroup(group);
socket.setTimeToLive(64);
socket.setLoopbackMode(true);
byte [] key = ident.getBytes("UTF-16BE");
byte [] request = Arrays.copyOf(key,key.length+2);
System.out.println(Arrays.toString(request));
DatagramPacket packet = new DatagramPacket(request, request.length, group, broadcastPort);
socket.send(packet);
byte [] res = new byte[1024];
packet = new DatagramPacket(res, res.length);
socket.receive(packet);
System.out.println(Arrays.toString(res));

这是我在iPhone上使用的发送代码。
NSData *toSend = [broadcastIdentifier dataUsingEncoding:NSUTF16BigEndianStringEncoding];
struct in_addr        localInterface;
struct sockaddr_in    groupSock;
int                   sd;
int                   datalen;

sd = socket(AF_INET, SOCK_DGRAM, 0);
memset((char *) &groupSock, 0, sizeof(groupSock));
groupSock.sin_family = AF_INET;
groupSock.sin_addr.s_addr = inet_addr(broadcastIP);
groupSock.sin_port = htons(broadcastPort);
localInterface.s_addr = INADDR_ANY;
setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, (char *)&localInterface, sizeof(localInterface));
sendto(sd, [toSend bytes], [toSend length], 0, (struct sockaddr*)&groupSock, sizeof(groupSock));

所以,为了澄清问题,我想知道为什么iPhone没有接收到数据包。同时,Robert完全正确,模拟器能够正常工作的原因是由于回环。


什么是问题?你期望什么,而又发生了什么意外的情况? - Nikolai Fetissov
3个回答

4
我假设这是接收方...从表面上看,多播套接字设置看起来很好。你说在模拟器上可以工作但在实际网络中无法工作,对吗?存在一个问题,你的网络设备,特别是任何路由器,可能需要显式设置以允许广播和/或多播数据包的转发。默认情况下,这些类型的数据包通常会在网络边缘被丢弃。还有一种可能性——如果在同一台计算机上运行发送方和接收方并关闭IP_MULTICAST_LOOP,则不会收到任何数据包,因为它禁用了多播环回接口。除非您提供更多有关您的设置和/或看到更多代码的信息,否则这就是我能想到的全部。

已添加了其余的网络代码,你关于回环是为什么它在模拟器上运行的100%正确。 - Jonathon

1
我需要将INADDR_ANY更改为广播IP...
struct sockaddr_in addressData;
memset(&addressData, 0, sizeof(addressData));
addressData.sin_len = sizeof(addressData);
addressData.sin_family = AF_INET;
addressData.sin_port = htons(broadcastPort);
addressData.sin_addr.s_addr = inet_addr(broadcastIP);
NSData *address = [NSData dataWithBytes:&addressData length:sizeof(addressData)];
if (kCFSocketSuccess != CFSocketSetAddress(advertiseSocket, (CFDataRef)address)) {
    [self stopBeforeStart];
    [self connectionFailed];
    return;
}

struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr(broadcastIP);         
mreq.imr_interface.s_addr = INADDR_ANY;

0

我曾经遇到一个类似的问题,试图通过 tcpreplay 在我的电脑上测试 Wireshark 记录下来的其他两台电脑 A 和 B 之间的一些数据交换。因此我这样修改了旧记录:

tcprewrite --pnat=A-IP:loIP,B-IP:loIP -i oldrecordfile -o newrecordfile

然后

tcpreplay -T nano --verbose -i lo newrecordfile

但是我的应用程序recv()失败了。

问题可能与tcpreplay的环回限制有关,因此我决定通过tcprewrite重新生成适当的记录文件,并将数据从PC重新发送到PC,在这一点上,tcpdump在我的接收端显示了预期的结果,但程序的recv()始终失败。

最后,我发现原因是两台PC之间的旧MAC地址和路由器的存在:

tcprewrite --pnat=oldA-IP:newA-IP,oldB-IP:newB-IP --enet-smac=newA-MAC,newA-MAC --enet-dmac=newB-MAC,newB-MAC -i oldrecordfile -o newrecordfile

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接