Соединение с и контроль сетевых служб
В этой главе описываются, как соединиться с Добрый день распространенной службой. Когда Вы, возможно, должны были бы соединиться со службой Bonjour, существует два случая:
Вы только что просмотрели для служб, и пользователь выбрал тот.
Вы сохранили информацию о службе для более позднего использования, и Вы явно инициализировали экземпляр
NSNetService
класс с той информацией.
В любом случае способом, которым Вы соединяетесь со службой, является то же. Существует три способа соединиться со службой, перечисленной в порядке предпочтения:
Используя метод подключения к службе на основе потоков такой как
getInputStream:outputStream:
.Соединение именем хоста.
Соединение IP-адресом.
Эти методы описаны в следующих разделах. Кроме того, раздел 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
экземпляр, Ваше приложение должно сделать четыре вещи:
Получите
NSNetService
экземпляр через инициализацию или открытие службы.Разрешите службу.
Реагируйте на сообщения, отправленные делегату объекта об адресах или ошибках.
Используйте получающиеся адреса или имя хоста для соединения со службой.
Получение и разрешение объекта 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-адресов вместо имени хоста. Существует несколько протестов, однако:
Если Вы работаете с IP-адресами непосредственно, несомненно, выполнят разрешение каждый раз, когда Вы используете службу, потому что несмотря на то, что имя службы является персистентным свойством, информация о сокете (IP-адреса и номер порта) может измениться от сеанса до сеанса.
В частности никогда не храните долгосрочную информацию IP-адреса. Если пользователь просматривает для службы и сохраняет ту службу, такой как в селекторе принтера, сохраните только имя службы, введите, и домен. Когда пора соединиться, эти значения могут быть разрешены в соответствующий IP-адрес и номер порта. Посмотрите Сохранение Добрый день Служба для получения дополнительной информации.
netServiceDidResolveAddress:
метод говорит делегату чтоNSNetService
объект добавил адрес к своему списку адресов для службы. Однако больше адресов может быть добавлено. Например, в системах, поддерживающих и IPv4 и IPv6,netServiceDidResolveAddress:
может быть вызван два или больше раза — один раз для адреса IPv4 и снова для адреса IPv6.В этом методе делегата, быть уверенным, что вся информация о соответствующем соединении присутствует прежде, чем попытаться соединиться со службой. Если часть информации отсутствует,
netServiceDidResolveAddress:
будет вызван позже. Если многократные адреса возвращаются из разрешения службы, попытайтесь соединиться с каждым перед отказом.
Сохранение добрый день служба
Если Ваше приложение должно постоянно хранить ссылку на службу Bonjour, такой как в селекторе принтера, сохранить только имя службы, введите, и домен. Путем сохранения только домена, введите и назовите информацию, Вы гарантируете, что Ваше приложение может найти соответствующую службу, даже если изменились ее IP-адреса или номер порта.
Когда Вы соединяетесь со службой позже, инициализируете NSNetService
объект с этой информацией путем вызова initWithDomain:type:name:
метод.
После инициализации объекта службы соединитесь со службой путем вызова getInputStream:outputStream:
метод или путем передачи результата a hostName
вызовите к функции подключения по имени или методу, как описано ранее в этой главе. Когда Вы делаете это, Добрый день автоматически разрешает имя хоста или домен, введите и назовите значения в текущие IP-адреса и номер порта для службы.
Контроль добрый день служба
В некоторых ситуациях приложение, возможно, должно знать определенную информацию о службе Bonjour, не будучи должен поддержать соединение с той службой. Например, программа чата могла бы позволить пользователю указать состояние (неактивный, далеко, и т.д.). Другие пользователи в сети должны быть в состоянии изучить эту новую информацию быстро без каждой необходимости машины постоянно опросить любую машину.
Для поддержки распространения такой информации, Добрый день позволяет каждой службе обеспечить произвольный DNS TXT
запись с дополнительной информацией. Если Вы хотите использовать эту функциональность в своем приложении, необходимо сделать следующее:
При публикации службы вызовите setTXTRecordData:
метод, чтобы установить или обновить данные связался со службой. Эти данные должны быть закодированы согласно разделу 3.3.14 из RFC 1035.
Самый прямой способ генерировать совместимые данные путем вызова dataFromTXTRecordDictionary:
с NSDictionary
объект. В том словаре сохраните только пары ключ/значение, которые Вы хотите сделать доступным для любого приложения, просматривающего для Вашей службы.
При просмотре для служб сделайте следующее:
Вызовите
startMonitoring
метод, чтобы начать контролироватьTXT
запись связалась с данной службой.
Реализуйте
netService:didUpdateTXTRecordData:
метод для получения уведомления каждый раз, когда службаTXT
рекордные изменения.Можно или обработать получающиеся данные сами или вызов
dictionaryFromTXTRecordData:
получитьNSDictionary
объект, содержащий предоставленные пары ключ/значение (часто от предыдущего вызова доdataFromTXTRecordDictionary:
).Вызовите
stopMonitoring
метод, когда Вы больше не хотите получить уведомления об изменениях в определенной службе.
Для получения дополнительной информации см. документацию для упомянутых выше методов.