简体   繁体   English

发送到IPv6地址时,GCDAsyncUdpSocket立即关闭

[英]GCDAsyncUdpSocket immediately closes when sending to an IPv6 address

I'm connecting via UDP to a server on a different device which is advertised by Bonjour. 我通过UDP连接到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. 当运行此代码的iOS设备和服务器都在我们的wifi网络上时,它工作正常,因为bonjour服务解析为我们的dhcp服务器发出的192.168.0.xxx地址。 However when it is advertised by bluetooth, sometimes the service resolves to 169.254.xxx.xxx (IPv4) in which case it works just fine. 然而,当它通过蓝牙做广告时,有时服务解析为169.254.xxx.xxx(IPv4),在这种情况下它可以正常工作。 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). 但有时它解析为fe80 :: xxxx:xxxx:xxxx:xxxx(IPv6),在这种情况下套接字连接(我收到udpSocket:didConnectToAddress回调)但在我尝试发送数据时立即关闭(我立即收到udpSocketDidClose:withError回调在致电发送时)。

- (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. 我的udpSocket:didConnectToAddress方法调用一个send,而我的其他回调基本上只是信息(NSLog)。 This is the NSError passed to udpSocketDidClose:withError : 这是传递给udpSocketDidClose:withError的NSError 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. 在修复这个问题时,我想让它与IPv6协同工作而不是强制IPv4 ...强迫IPv4对我来说似乎很脆弱。

fe80 is a link-local IPv6 address. fe80是链路本地IPv6地址。 The machine to which you're connecting must have more than one network interface -- most do, eg Ethernet and WiFi. 您要连接的机器必须具有多个网络接口 - 大部分都是,例如以太网和WiFi。 To fully specific an IPv6 address, the scope_id is required. 要完全特定IPv6地址,scope_id是必需的。 This is the sin6_scope_id from: 这是来自的sin6_scope_id:

// 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 当与地址结合并转换为字符串时,如下所示: fe80::e2f8:47ff:fe23:5392%eth1

When the DNS is resolved, the NSData wrapping a sockaddr struct includes this information. 解析DNS后,包装sockaddr结构的NSData包含此信息。 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). 然而,在你的代码,你提取sin6_portsin6_addr ,然后喂它们回到GCDAsyncUDPSocket泯灭的sin6_flowinfo (你不需要)和sin6_scope_id (在这种情况下,你这样做)。

Use -[GCDAsyncUDPSocket connectToAddress:error:] directly, using the NSData you get directly from your resolve service, and you should be good to go. 直接使用-[GCDAsyncUDPSocket connectToAddress:error:] ,使用直接从您的解析服务获得的NSData ,您应该好好去。

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. 我所做的是在套接字上调用setPreferIPv4setIPv6Enabled:FALSE ,如果DNS查找仅返回IPv6地址,则会导致连接失败。 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. 然后,在udpSocket:didNotConnect:我检查了该特定错误( IPv6 has been disabled and DNS lookup found no IPv4 address(es). )如果连接因此失败,请返回我的setupConnection方法并再次尝试。 Eventually the DNS lookup returns an IPv4 address and things proceed smoothly from there. 最终DNS查找返回一个IPv4地址,事情从那里顺利进行。

This isn't the most elegant solution, but it works. 这不是最优雅的解决方案,但它确实有效。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM