【问题标题】:DNS resolution with IPv6 on iOS only synchronous?仅在 iOS 上使用 IPv6 进行 DNS 解析同步?
【发布时间】:2011-08-29 09:49:57
【问题描述】:

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

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

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

遗憾的是,iOS (iPhone) 仅包含一个私有版本的 NSHost。

我发现了许多使用其他对象或方法执行此操作的方法,但所有这些方法都只在结果中得到 IPv4 地址。所以这是目前我找到的唯一有效的方法。

我第一次尝试像bdunagan一样使用异步CFHostStartInfoResolution,但未能使其适应IPv6。

你们中的一些人会喜欢让一个方法工作,所以这里是一个,但如果你知道一种异步的方法,我会很高兴了解它......因为目前我使用弹出窗口来提醒蜂窝连接速度较慢时可能发生的下一次冻结

/**
 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。

【问题讨论】:

  • ...为什么不能在后台线程上执行该选择器?也许我有点密集......
  • 我开始阅读有关线程的 Apple 文档,它们(不是一点点)密集。还没有找到一个简单的例子。你知道线程吗?我开始认为文档的复杂性不值得花时间。如果您认为这是解决方案,我会这样做并更正这篇文章。

标签: iphone objective-c ios dns ipv6


【解决方案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,它更强大,因此需要更多工作。不过,它仍然比显式线程管理更容易!

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

【讨论】:

  • 非常感谢您,我正在阅读文档中的十几页,您的简单示例一开始就更清晰了,我会看看 NSOperation。一旦我让它工作,我编辑这个问题。
  • 很高兴我能帮上忙!您应该保持原样。 StackOverflow 格式是为了让问题发布保持单一、特定的问题和答案帖子以包含解决方案。仅当您需要澄清或添加细节时才编辑您的问题,以便获得更好的答案。如果您希望 me 特别澄清或扩展我的回答中的某些元素,请在评论中提问。
【解决方案2】:

感谢 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];
}

然后 - 在模型中 - 我在对委托进行任何操作之前对其进行测试。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-30
    • 2011-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多