Соединение с и контроль сетевых служб

В этой главе описываются, как соединиться с Добрый день распространенной службой. Когда Вы, возможно, должны были бы соединиться со службой Bonjour, существует два случая:

В любом случае способом, которым Вы соединяетесь со службой, является то же. Существует три способа соединиться со службой, перечисленной в порядке предпочтения:

Эти методы описаны в следующих разделах. Кроме того, раздел Persisting a Bonjour Service описывает, как должным образом хранить информацию о службе для более позднего повторного использования, и раздел Monitoring a Bonjour Service объясняет, как служба Bonjour может предоставить дополнительную информацию, которую клиенты могут пассивно контролировать, не соединяясь со службой.

Соединение с потоками

Если Вы соединяете необработанный поток TCP Основы использования API (в противоположность высокоуровневому API, такой как NSURLConnection), самым прямым способом соединиться со службой является с подключением к службе API, такой как getInputStream:outputStream:.

Этот метод обеспечивает ссылку на входной поток (NSInputStream) и поток вывода (NSOutputStream), оба из которых можно получить доступ синхронно или асинхронно. Для взаимодействия асинхронно необходимо запланировать потоки в текущем цикле выполнения и присвоить их объект делегата. Эти потоки обеспечивают средние значения общего назначения связи с основанными на TCP сетевыми службами, не связывающейся ни к какому определенному протоколу (такому как HTTP).

Перечисление 4-1 демонстрирует, как соединиться с сетевой службой (NSNetService) использование потоков. Обратите внимание на то, что при требовании только одного из этих двух потоков необходимо передать NULL для другого параметра так, чтобы Вы не возвращали другой поток. Для полного примера использования разрешения службы NSStream объекты, см. проект примера кода PictureSharing в Библиотеке Разработчика Mac.

Перечисление 4-1  , Соединяющееся с разрешенным Добрый день сетевая служба

#import <sys/socket.h>
#import <netinet/in.h>
 
// ...
 
NSNetService *service;  // Assume this exists. For instance, you may
                        // have received it from an NSNetServiceBrowser
                        // delegate callback.
 
NSInputStream *istream = nil;
NSOutputStream *ostream = nil;
 
[service getInputStream:&istream outputStream:&ostream];
if (istream && ostream)
{
    // Use the streams as you like for reading and writing.
}
else
{
    NSLog(@"Failed to acquire valid streams");
}

Соединение по имени

Если Вы соединяетесь со службой Bonjour с помощью высокоуровневого API такой как NSURLConnection, необходимо соединить использование имени хоста службы.

Можно получить имя хоста для службы Bonjour путем разрешения службы и затем вызова hostName метод на NSNetService объект. Затем передайте то имя хоста и порт к надлежащему API, так же, как Вы были бы любое другое имя хоста.

Процесс разрешения

Когда Добрый день разрешает службу, она делает две вещи:

  • Ищет информацию об имени службы для получения имени хоста и номера порта.

  • Ищет имя хоста для обеспечения ряда IP-адресов.

Если служба недоступна, поскольку разрешение может занять время, особенно NSNetService решения асинхронно, предоставляя информацию Вашему приложению через объект делегата.

Разрешить и использовать NSNetService экземпляр, Ваше приложение должно сделать четыре вещи:

  1. Получите NSNetService экземпляр через инициализацию или открытие службы.

  2. Разрешите службу.

  3. Реагируйте на сообщения, отправленные делегату объекта об адресах или ошибках.

  4. Используйте получающиеся адреса или имя хоста для соединения со службой.

Получение и разрешение объекта NSNetService

Можно получить NSNetService объект, представляющий службу, Вы хотите соединиться с одним из двух способов:

  • Использовать NSNetServiceBrowser обнаружить службы.

  • Инициализируйте новое NSNetService объект с именем, введите, и домен службы, которая, как известно, существует, обычно сохраняется от предыдущего сеанса просмотра.

Для получения информации о просмотре службы посмотрите Просмотр для Сетевых служб.

Создать NSNetService объект для разрешения, а не публикации, используйте initWithDomain:type:name: метод.

Как только Вы имеете NSNetService возразите, чтобы разрешить, присвоить его делегат и использовать resolveWithTimeout: метод для асинхронного разрешения службы. Когда разрешение завершено, делегат получает a netServiceDidResolveAddress: сообщение на успех или a netService:didNotResolve: обменивайтесь сообщениями, если произошла ошибка. Поскольку делегат получает идентификационные данные NSNetService возразите как часть метода делегата, один делегат может служить многократный NSNetService объекты.

Перечисление 4-2 демонстрирует, как инициализировать и решить NSNetService объект для гипотетической совместно использующей музыку службы. Код инициализирует объект с именем serviceName, с типом _music._tcp, и с локальным для ссылки суффиксом local.. Это тогда присваивает его делегат и просит, чтобы он разрешил имя в адреса сокета.

Перечисление 4-2  , Разрешающее сетевые службы с NSNetService

id delegateObject = [[NetServiceResolutionDelegate alloc] init]; // Defined below
NSString *serviceName = ...;
NSNetService *service;
 
service = [[NSNetService alloc] initWithDomain:@"local." type:@"_music._tcp"
                                name:serviceName];
[service setDelegate:delegateObject];
[service resolveWithTimeout:5.0];

Реализация методов делегата для разрешения

NSNetService разрешение возвратов заканчивается своему делегату. При разрешении службы объект делегата должен реализовать следующие методы:

  • netServiceDidResolveAddress:

  • netService:didNotResolve:

Предположение, что Вы соединяетесь именем хоста, можно запросить ту информацию как только делегат netServiceDidResolveAddress: сначала вызывается. Будьте осторожны, тем не менее, потому что этот метод можно вызвать несколько раз. (Причина объяснена далее в Соединении с Добрый день Служба IP-адресом.)

Если разрешение перестало работать по какой-либо причине, netService:didNotResolve: метод вызывают. Если делегат получает a netService:didNotResolve: сообщение, необходимо извлечь тип ошибки из возвращенного словаря с помощью NSNetServicesErrorCode ключ и обрабатывает ошибку соответственно. Для списка возможных ошибок посмотрите Ссылку класса NSNetService.

Перечисление 4-3 показывает интерфейс для класса, действующего как делегат к кратному числу NSNetService объекты и Перечисление 4-4 показывают его реализацию. Можно использовать этот код в качестве начальной точки для более сложного отслеживания разрешенных служб.

  Интерфейс перечисления 4-3 для NSNetService делегируйте объект, используемый при разрешении службы

#import <Foundation/Foundation.h>
 
@interface NetServiceResolutionDelegate : NSObject <NSNetServiceDelegate>
{
    // Keeps track of services handled by this delegate
    NSMutableArray *services;
}
 
// Other methods
- (BOOL)addressesComplete:(NSArray *)addresses
        forServiceType:(NSString *)serviceType;
- (void)handleError:(NSNumber *)error withService:(NSNetService *)service;
 
@end

  Реализация перечисления 4-4 для NSNetService делегируйте объект (разрешение)

#import "NetServiceResolutionDelegate.h"
 
@implementation NetServiceResolutionDelegate
 
- (id)init
{
    self = [super init];
    if (self) {
        services = [[NSMutableArray alloc] init];
    }
    return self;
}
 
// Sent when addresses are resolved
- (void)netServiceDidResolveAddress:(NSNetService *)netService
{
    // Make sure [netService addresses] contains the
    // necessary connection information
    if ([self addressesComplete:[netService addresses]
            forServiceType:[netService type]]) {
        [services addObject:netService];
    }
}
 
// Sent if resolution fails
- (void)netService:(NSNetService *)netService
        didNotResolve:(NSDictionary *)errorDict
{
    [self handleError:[errorDict objectForKey:NSNetServicesErrorCode] withService:netService];
    [services removeObject:netService];
}
 
// Verifies [netService addresses]
- (BOOL)addressesComplete:(NSArray *)addresses
        forServiceType:(NSString *)serviceType
{
    // Perform appropriate logic to ensure that [netService addresses]
    // contains the appropriate information to connect to the service
    return YES;
}
 
// Error handling code
- (void)handleError:(NSNumber *)error withService:(NSNetService *)service
{
    NSLog(@"An error occurred with service %@.%@.%@, error code = %d",
        [service name], [service type], [service domain], [error intValue]);
    // Handle error here
}
 
@end

Когда разрешение перестало работать

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

Поскольку сбой показать допустимую службу является большей проблемой для пользователя, чем показ устаревшей службы, Добрый день сознательно допускает ошибку на стороне предположения, что служба все еще доступна. Это означает, что существует много способов, которыми служба может уйти без Вашего приложения, сразу являющегося знающим об этом:

  • Устройство может быть резко выключено, или питание может перестать работать.

  • Устройство может быть отключено от проводной сети.

  • Устройство может переместиться из диапазона базовой станции беспроводной связи.

  • Потеря пакетов может предотвратить «до свидания» пакет от достижения его места назначения.

В результате несмотря на то, что Добрый день обычно обнаруживает новые службы в течение нескольких секунд, если служба уходит, исчезновение службы не может быть обнаружено, пока Ваше приложение не пытается соединиться с ним и не получает ответа.

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

Минимизировать влияние устаревших записей:

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

  • Если разрешение службы возвращает результат, но клиент неспособен открыть соединение TCP для него в течение 5–10 секунд, низкоуровневые C клиенты должны вызвать DNSServiceReconfirmRecord. Этот вызов говорит Добрый день, что именованная служба не отвечает и что данные кэша Бонджура для службы могут быть устаревшими. Перечисление 4-5 показывает пример того, как использовать DNSServiceReconfirmRecord функция.

Соблюдение этих правил помогает Добрый день удалить устаревшие службы из списков просмотра быстрее.

  Пример перечисления 4-5 вызова DNSServiceReconfirmRecord

            // serviceName should be in the form
            // "name.service.protocol.domain.".  For example:
            // "MyLaptop._ftp._tcp.local."
            NSString *serviceName = ...;
            NSString *              serviceName;
            NSArray *               serviceNameComponents;
            NSUInteger              serviceNameComponentsCount;
            serviceNameComponents = [serviceName componentsSeparatedByString:@"."];
            serviceNameComponentsCount = [serviceNameComponents count];
            if ( (serviceNameComponentsCount >= 5) && ([serviceNameComponents[serviceNameComponentsCount - 1] length] == 0) ) {
                protocol = [serviceNameComponents[2] lowercaseString];
                if ( [protocol isEqual:@"_tcp"] || [protocol isEqual:@"_udp"] ) {
                    fullname = [[serviceNameComponents subarrayWithRange:NSMakeRange(1, serviceNameComponentsCount - 1)] componentsJoinedByString:@"."];
                    retVal = EXIT_SUCCESS;
                    NSMutableData *     recordData;
 
                    recordData = [[NSMutableData alloc] init];
                    for (NSString * label in serviceNameComponents) {
                        const char *    labelStr;
                        uint8_t         labelStrLen;
 
                        labelStr = [label UTF8String];
                        if (strlen(labelStr) >= 64) {
                            fprintf(stderr, "%s: label too long: %s\n", getprogname(), labelStr);
                            retVal = EXIT_FAILURE;
                            break;
                        } else {
                            // cast is safe because of length check
                            labelStrLen = (uint8_t) strlen(labelStr);
 
                            [recordData appendBytes:&labelStrLen length:sizeof(labelStrLen)];
                            [recordData appendBytes:labelStr length:labelStrLen];
                        }
                    }
 
                    if ( (retVal == EXIT_SUCCESS) && ([recordData length] >= 256) ) {
                        fprintf(stderr, "%s: record data too long\n", getprogname());
                        retVal = EXIT_FAILURE;
                    }
 
                    if (retVal == EXIT_SUCCESS) {
                        err = DNSServiceReconfirmRecord(
                            0,
                            interfaceIndex,
                            [fullname UTF8String],
                            kDNSServiceType_PTR,
                            kDNSServiceClass_IN,
                            // cast is safe because of recordData length check above
                            (uint16_t) [recordData length],
                            [recordData bytes]
                        );
                        if (err != kDNSServiceErr_NoError) {
                            fprintf(stderr, "%s: reconfirm record error: %d\n", getprogname(), (int) err);
                            retVal = EXIT_FAILURE;
                        }
                    }
                }
            }

Соединение с добрый день службой IP-адресом

Как правило Вы не должны разрешать службу к IP-адресу и номеру порта, если Вы не делаете что-то очень необычное. В динамическом мире современных сетей IP-адреса могут измениться в любое время. Далее, данная служба Bonjour может иметь больше чем один IP-адрес, и не все они может быть достижимым. Путем соединения использующий имена хоста, Вы избегаете этих проблем. Для получения дополнительной информации читайте Избегают Разрешать Имена DNS прежде, чем Соединиться с Узлом в Сетевом Обзоре.

В редких экземплярах, когда соединение IP-адресом требуется, можно сделать так путем выполнения шагов для разрешения службы Bonjour в Соединении по имени и затем использовании IP-адресов вместо имени хоста. Существует несколько протестов, однако:

Сохранение добрый день служба

Если Ваше приложение должно постоянно хранить ссылку на службу Bonjour, такой как в селекторе принтера, сохранить только имя службы, введите, и домен. Путем сохранения только домена, введите и назовите информацию, Вы гарантируете, что Ваше приложение может найти соответствующую службу, даже если изменились ее IP-адреса или номер порта.

Когда Вы соединяетесь со службой позже, инициализируете NSNetService объект с этой информацией путем вызова initWithDomain:type:name: метод.

После инициализации объекта службы соединитесь со службой путем вызова getInputStream:outputStream: метод или путем передачи результата a hostName вызовите к функции подключения по имени или методу, как описано ранее в этой главе. Когда Вы делаете это, Добрый день автоматически разрешает имя хоста или домен, введите и назовите значения в текущие IP-адреса и номер порта для службы.

Контроль добрый день служба

В некоторых ситуациях приложение, возможно, должно знать определенную информацию о службе Bonjour, не будучи должен поддержать соединение с той службой. Например, программа чата могла бы позволить пользователю указать состояние (неактивный, далеко, и т.д.). Другие пользователи в сети должны быть в состоянии изучить эту новую информацию быстро без каждой необходимости машины постоянно опросить любую машину.

Для поддержки распространения такой информации, Добрый день позволяет каждой службе обеспечить произвольный DNS TXT запись с дополнительной информацией. Если Вы хотите использовать эту функциональность в своем приложении, необходимо сделать следующее:

При публикации службы вызовите setTXTRecordData: метод, чтобы установить или обновить данные связался со службой. Эти данные должны быть закодированы согласно разделу 3.3.14 из RFC 1035.

Самый прямой способ генерировать совместимые данные путем вызова dataFromTXTRecordDictionary: с NSDictionary объект. В том словаре сохраните только пары ключ/значение, которые Вы хотите сделать доступным для любого приложения, просматривающего для Вашей службы.

При просмотре для служб сделайте следующее:

Для получения дополнительной информации см. документацию для упомянутых выше методов.