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

发布于 2024-11-08 13:58:42 字数 4170 浏览 3 评论 0原文

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

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

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

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

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

我首先尝试使用异步 CFHostStartInfoResolution ,就像 bdunagan,但未能适配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。

(This is a work in progress. I wonder if someone could to improve it)

in Objective C, it's easy to resolve a hostname with NSHost.

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

Sadly iOS (iPhone) contains only a private version of NSHost.

I found many ways of doing this with other Objects or methods, but all of them got only IPv4 addresses in the results. So here is for the moment the only efficient method I have found.

I first tried to use the asynchronous CFHostStartInfoResolution as did bdunagan, but failed to adapt it to IPv6.

Some of you will appreciate to get a method working, so here is one, but if you know a way which would be Asynchronous I would appreciate to learn about it... cause for the moment I use a Popup to alert about the next freeze that could occur with slow cellular connection

/**
 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];
}

NB: I noticed that most of the time only 1 IPv6 is returned, I suspect it's due to iOS Memory optimisation when locally cached. if you run this method again and again, sometime you have 3 IPv6, but then you have only 1 IPv4.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

↙温凉少女 2024-11-15 13:58:42

如果您希望方法在后台线程上运行,最简单的方法是使用 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,它更强大,因此需要更多工作。不过,它仍然比显式线程管理更容易!

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

If you want a method to run on a background thread, the simplest way is to use performSelectorInBackground:withObject:; this is an instance method of NSObject, so any object can use it without any extra work (including, interestingly enough, class objects, which is good in this case because this is a class method):

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

Inside the method, you will need to set up an autorelease pool for the thread. You will also need some kind of callback method set up to get the return value back to your main thread. Make sure that you don't try to do any GUI activity on the background thread. It's only safe to do that on the main thread.

+ (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];
}

If you were not on the main thread to begin with, or if you needed more control, you could also look into NSOperation, which is more powerful and therefore requires more work. It's still easier than explicit thread management, though!

Hope that solves your problem. It sounded like you have this method doing what you need, you just need it to not block the main thread.

长伴 2024-11-15 13:58:42

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

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

thanks to Josh I could do it, but here is what I had to do :

Instead of calling directly

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

I call

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

And create 3 new methods:

- (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)
}

Edit:
In practice I use all of that in a Model, so I had a crash when I close the View before the end of the resolution

So in the view I added in dealloc what is necessary to avoid a crash

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

Then - in the model - I test delegate before doing anything with it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文