问题
I'm connecting via UDP to a server on a different device which is advertised by Bonjour. When both the iOS device which this code is running on, and the server, are on our wifi network it works just fine because the bonjour service resolves to a 192.168.0.xxx address which our dhcp server hands out. However when it is advertised by bluetooth, sometimes the service resolves to 169.254.xxx.xxx (IPv4) in which case it works just fine. But sometimes it resolves to fe80::xxxx:xxxx:xxxx:xxxx (IPv6) in which case the socket connects (I receive the udpSocket:didConnectToAddress
callback) but immediately closes when I try sending data (I receive the udpSocketDidClose:withError
callback immediately upon calling send).
- (BOOL) setupConnection: (DNSSDService*) service
{
NSString *host = [service resolvedHost];
NSUInteger port = [service resolvedPort];
NSLog(@"in setupConnection: host %@ port %u",
host, port);
self.sock = [[GCDAsyncUdpSocket alloc]initWithDelegate:self
delegateQueue:dispatch_get_main_queue() ];
NSError *err = nil;
if (![self.sock connectToHost:host onPort:port error:&err]) {
NSLog(@"we goofed: %@", err);
return NO;
}
return YES;
}
My udpSocket:didConnectToAddress
method calls a send, and my other callbacks are basically just informational (NSLog) at this point. This is the NSError passed to udpSocketDidClose:withError
:
Error Domain=GCDAsyncUdpSocketErrorDomain Code=4 "Socket closed" UserInfo=0x2630c0 {NSLocalizedDescription=Socket closed}
Less than useful.
In fixing this I'd like to make it work with IPv6 instead of force IPv4... forcing IPv4 just seems fragile to me.
回答1:
fe80 is a link-local IPv6 address. The machine to which you're connecting must have more than one network interface -- most do, e.g. Ethernet and WiFi. To fully specific an IPv6 address, the scope_id is required. This is the sin6_scope_id from:
// IPv6 AF_INET6 sockets:
struct sockaddr_in6 {
u_int16_t sin6_family; // address family, AF_INET6
u_int16_t sin6_port; // port number, Network Byte Order
u_int32_t sin6_flowinfo; // IPv6 flow information
struct in6_addr sin6_addr; // IPv6 address
u_int32_t sin6_scope_id; // Scope ID
};
and when combined with the address and converted to a string looks like this: fe80::e2f8:47ff:fe23:5392%eth1
When the DNS is resolved, the NSData
wrapping a sockaddr
struct includes this information. However, in your code, you are extracting the sin6_port
and sin6_addr
, then feeding them back to GCDAsyncUDPSocket
devoid of the sin6_flowinfo
(which you don't need) and the sin6_scope_id
(which in this case you do).
Use -[GCDAsyncUDPSocket connectToAddress:error:]
directly, using the NSData
you get directly from your resolve service, and you should be good to go.
回答2:
What I did was call setPreferIPv4
and setIPv6Enabled:FALSE
on the socket, which would make connecting fail if the DNS lookup only returned an IPv6 address. Then, in udpSocket:didNotConnect:
i checked for that specific error (IPv6 has been disabled and DNS lookup found no IPv4 address(es).
) and if the connect failed for that reason, went back into my setupConnection
method and tried again. Eventually the DNS lookup returns an IPv4 address and things proceed smoothly from there.
This isn't the most elegant solution, but it works.
来源:https://stackoverflow.com/questions/13570444/gcdasyncudpsocket-immediately-closes-when-sending-to-an-ipv6-address