iOS上的IPv6 DNS解析只能同步吗?

时间:2011-05-20 06:02:23

标签: iphone objective-c ios dns ipv6

(这是一项正在进行中的工作。我想知道是否有人可以改进它)

在Objective C中,使用NSHost很容易解析主机名。

[[NSHost hostWithName:@"www.google.com"] address]

可悲的是,iOS(iPhone)只包含NSHost的私有版本。

我发现很多方法可以使用其他对象或方法,但是所有这些方法都只在结果中获得了IPv4地址。所以现在这是我找到的唯一有效方法。

我首先尝试使用异步CFHostStartInfoResolution和bdunagan,但无法使其适应IPv6。

你们中的一些人会很高兴得到一个方法,所以这里有一个,但是如果你知道一种异步的方法我会很高兴了解它...因为目前我使用Popup提醒一下细胞连接缓慢可能发生下一次冻结

/**
 Give the IPs corresponding to a Hostname

 Sometime only 1 IPv4 is shown even if there's more.
 Sometime only 1 IPv6 is shown even if there's more.
 Certainly due to iOS Memory optimisation when locally cached

 @author Christian Gonzalvez, http://wiki.gonzofamily.com
 @param hostName A hostname
 @return an Array of NSString of all the corresponding IP addresses. The first
 is the Canonical name, the following are IPs (all NSString)
 */
+ (NSArray *)addressesForHostname:(NSString *)hostname
{
    const char* hostnameC = [hostname UTF8String];

    struct addrinfo hints, *res;
    struct sockaddr_in *s4;
    struct sockaddr_in6 *s6;
    int retval;
    char buf[64];
    NSMutableArray *result; //the array which will be return
    NSMutableArray *result4; //the array of IPv4, to order them at the end
    NSString *previousIP = nil;

    memset (&hints, 0, sizeof (struct addrinfo));
    hints.ai_family = PF_UNSPEC;//AF_INET6;
    hints.ai_flags = AI_CANONNAME;
        //AI_ADDRCONFIG, AI_ALL, AI_CANONNAME,  AI_NUMERICHOST
        //AI_NUMERICSERV, AI_PASSIVE, OR AI_V4MAPPED

    retval = getaddrinfo(hostnameC, NULL, &hints, &res);
    if (retval == 0)
      {

        if (res->ai_canonname)
          {
            result = [NSMutableArray arrayWithObject:[NSString stringWithUTF8String:res->ai_canonname]];
          }
        else
          {
                //it means the DNS didn't know this host
            return nil;
          }
        result4= [NSMutableArray array];
        while (res) {
            switch (res->ai_family){
                case AF_INET6:              
                    s6 = (struct sockaddr_in6 *)res->ai_addr;
                    if(inet_ntop(res->ai_family, (void *)&(s6->sin6_addr), buf, sizeof(buf))
                       == NULL)
                      {
                        NSLog(@"inet_ntop failed for v6!\n");
                      }
                    else
                      {
                            //surprisingly every address is in double, let's add this test
                        if (![previousIP isEqualToString:[NSString stringWithUTF8String:buf]]) {
                            [result addObject:[NSString stringWithUTF8String:buf]];
                        }
                      }
                    break;

                case AF_INET:               
                    s4 = (struct sockaddr_in *)res->ai_addr;
                    if(inet_ntop(res->ai_family, (void *)&(s4->sin_addr), buf, sizeof(buf))
                       == NULL)
                      {
                        NSLog(@"inet_ntop failed for v4!\n");
                      }
                    else
                      {
                            //surprisingly every address is in double, let's add this test
                        if (![previousIP isEqualToString:[NSString stringWithUTF8String:buf]]) {
                            [result4 addObject:[NSString stringWithUTF8String:buf]];
                        }
                      }
                    break;
                default:
                    NSLog(@"Neither IPv4 nor IPv6!");

            }
                //surprisingly every address is in double, let's add this test
            previousIP = [NSString stringWithUTF8String:buf];

            res = res->ai_next;
        }
      }else{
          NSLog(@"no IP found");
          return nil;
      }

    return [result arrayByAddingObjectsFromArray:result4];
}

注意:我注意到大部分时间只返回1个IPv6,我怀疑这是由于本地缓存时的iOS内存优化。如果你一次又一次地运行这个方法,有时你有3个IPv6,但是你只有1个IPv4。

2 个答案:

答案 0 :(得分:1)

如果您希望方法在后台线程上运行,最简单的方法是使用performSelectorInBackground:withObject:;这是NSObject的实例方法,因此任何对象都可以使用它而无需任何额外的工作(包括,有趣的是, class 对象,这在这种情况下很好,因为这是一个类方法):

[[self class] performSelectorInBackground:@selector(addressesForHostName:) 
                               withObject:theHostName];

在方法内部,您需要为线程设置自动释放池。您还需要设置某种回调方法,以将返回值返回到主线程。确保您不尝试在后台线程上执行任何GUI活动。在主线程上执行此操作是唯一安全的。

+ (NSArray *)addressesForHostname:(NSString *)hostname
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // Do your stuff...

    // Wait until done to allow memory to be managed properly
    // If we did not do this, the array might be deallocated
    // before the main thread had a chance to retain it
    [self performSelectorOnMainThread:@selector(addressesCallback:)
                              withObject:[result arrayByAddingObjectsFromArray:result4]
                           waitUntilDone:YES];
    // Inside a class method, self refers to the class object.

    [pool drain];
}

如果您没有开始使用主线程,或者您需要更多控制权,那么您还可以查看NSOperation,它更强大,因此需要更多工作。但它仍然比显式线程管理更容易!

希望能解决您的问题。这听起来好像你有这个方法做你需要的,你只需要它不要阻止主线程。

答案 1 :(得分:1)

感谢Josh我能做到,但这就是我必须要做的事情:

而不是直接打电话

self.ipAddressesString = [CJGIpAddress addressesForHostname:@"www.google.com"];

我打电话

[self resolveNow:@"www.google.com"];

创建3种新方法:

- (void)resolveNow:(NSString *)hostname
{
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
    [self performSelectorInBackground:@selector(hostname2ipAddresses:) 
                                   withObject:hostname];
}

- (void)hostname2ipAddresses:(NSString *)hostname
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
      //Here is my previous lonely line !! safely started in an other thread
    self.ipAddressesString = [CJGIpAddress addressesForHostname:hostname];
    [self performSelectorOnMainThread:@selector(resolutionDidFinish)
                           withObject:nil
                        waitUntilDone:YES];
    [pool drain];
}

- (void)resolutionDidFinish
{
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
    //My STUFF with self.ipAddressesString (now filled)
}

编辑: 在实践中,我在模型中使用了所有这些,所以当我在分辨率结束前关闭视图时发生了崩溃

所以在视图中我在dealloc中添加了避免崩溃的必要条件

- (void)dealloc
{
    self.model.delegate = nil;
    [super dealloc];
}

然后 - 在模型中 - 我在用它做任何事之前测试委托。