Разрешение DNS с IPv6 на iOS только синхронно?

(Работа в процессе. Интересно, может ли кто-нибудь улучшить ее)

в 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];
}

NB: я заметил, что в большинстве случаев возвращается только 1 IPv6, я подозреваю, что это связано с оптимизацией памяти iOS при локальном кэшировании. если вы запускаете этот метод снова и снова, иногда у вас есть 3 IPv6, но тогда у вас есть только 1 IPv4.


person chriscatfr    schedule 20.05.2011    source источник
comment
... почему вы не можете просто выполнить этот селектор в фоновом потоке? Возможно, я немного туплю...   -  person lxt    schedule 20.05.2011
comment
Я начал читать документацию Apple о потоках, они (немного) плотные. Простого примера пока не нашел. Вы знаете о нитях? Я начал думать, что это не стоит времени, необходимого из-за сложности документа. Если вы думаете, что это решение, я сделаю это и исправлю этот пост.   -  person chriscatfr    schedule 20.05.2011


Ответы (2)


Если вы хотите, чтобы метод выполнялся в фоновом потоке, самый простой способ — использовать performSelectorInBackground:withObject:; это метод экземпляра NSObject, поэтому любой объект может использовать его без дополнительной работы (включая, что интересно, объекты класса, что хорошо в данном случае, потому что это метод класса):

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

Внутри метода вам нужно будет настроить пул автозапуска для потока. Вам также понадобится какой-то метод обратного вызова, настроенный для возврата возвращаемого значения в ваш основной поток. Убедитесь, что вы не пытаетесь выполнять какие-либо действия с графическим интерфейсом в фоновом потоке. Безопасно делать это только в основном потоке.

+ (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, который является более мощным и требует дополнительной работы. Однако это все же проще, чем явное управление потоками!

Надеюсь, это решит вашу проблему. Похоже, у вас есть этот метод, делающий то, что вам нужно, вам просто нужно, чтобы он не блокировал основной поток.

person jscs    schedule 20.05.2011
comment
Отлично, спасибо, я читал подростки страниц из документа, и ваш простой пример для начала намного понятнее, я посмотрю на NSOperation. Как только я заработал, я редактирую этот вопрос. - person chriscatfr; 21.05.2011
comment
Рад, что смог помочь! Вы должны оставить вопрос как есть. Формат StackOverflow предназначен для того, чтобы публикация вопросов оставалась одной конкретной проблемой, а сообщения с ответами содержали решения. Редактируйте свой вопрос только в том случае, если вам нужно уточнить или добавить детали, чтобы получить лучший ответ. Если вы хотите, чтобы я особенно прояснил или расширил какой-то элемент моего ответа, просто спросите в комментарии. - person jscs; 21.05.2011

благодаря Джошу я смог это сделать, но вот что мне пришлось сделать:

Вместо прямого вызова

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

Затем - в модели - я тестирую делегата, прежде чем что-либо с ним делать.

person chriscatfr    schedule 23.05.2011