Разрешение DNS с IPv6 на iOS только синхронно? - PullRequest
2 голосов
/ 20 мая 2011

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

в 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.

Ответы [ 2 ]

1 голос
/ 23 мая 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)
}

Edit: На практике я использую все это в модели, поэтому у меня произошел сбой при закрытии вида до конца разрешения

Так что в представлении я добавил в dealloc то, что необходимо, чтобы избежать сбоя

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

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

1 голос
/ 20 мая 2011

Если вы хотите, чтобы метод выполнялся в фоновом потоке, самый простой способ - использовать 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, который является более мощным и поэтому требует больше работы. Это все же проще, чем явное управление потоками!

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

...