Предотвращение общих сетевых ошибок
При записи основанного на сетях программного обеспечения разработчики часто делают некоторых общим проектом и ошибками использования, которые могут вызвать серьезные проблемы производительности, катастрофические отказы и другое неправомерное поведение. Эта глава выделяет несколько из тех ошибок и описывает, как избежать или фиксировать их.
Очистите свои соединения
Соединения TCP остаются открытыми, пока или соединение явно не закрывается или тайм-аут, происходит. Если проверка активности TCP не включена для соединения, тайм-аут происходит, только если существуют данные, ожидающие, чтобы быть переданными, который не может быть поставлен. Это означает, что, если Вы не закрываете свои неактивные соединения TCP, они останутся открытыми до Ваших выходов программы.
Рекомендуемый способ включить проверку активности TCP путем установки SO_KEEPALIVE
флаг на сокете с setsockopt
.
Избегите Сокетов POSIX и CFSocket на iOS, Где Возможно
Используя сокеты POSIX непосредственно имеет и преимущества и недостатки относительно использования их через высокоуровневый API. Основные преимущества:
Сокеты значительно упрощают сетевой код портирования до и с платформ не-Apple.
Можно поддерживать протоколы кроме TCP.
Основные недостатки:
Сокеты имеют много сложностей, обрабатывающихся для Вас высокоуровневым APIs. Таким образом необходимо будет записать больше кода, обычно означающего больше ошибок.
В iOS, с помощью снабжает непосредственно использование сокетом функции POSIX или
CFSocket
автоматически не активирует сотовый модем устройства или по требованию VPN.
Наиболее подходящее время для использования сокетов непосредственно - при разработке межплатформенного инструмента или высокоэффективного программного обеспечения сервера. При других обстоятельствах обычно необходимо использовать высокоуровневый API.
Избегите синхронных сетевых запросов к основному потоку
При выполнении сетевых операций на основном потоке необходимо использовать только асинхронные вызовы.
Сетевая связь по сути подвержена задержкам. Например, испытывающий таймаут запрос DNS может взять вверх половины минуты и a connect
может взять еще дольше. Если Вы выполняете синхронный сетевой вызов — вызов, ожидающий ответа и затем возвращающий данные — поток, выполнивший вызов, становится блокированным в ядре до работы или завершается или перестал работать. Если тот поток, оказывается, основной поток Вашей программы, Ваша программа становится безразличной.
В OS X приложение GUI это вызывает вращение, ожидают курсор для появления. Меню становятся безразличными, щелчки и нажатия клавиш задерживаются или теряются, и Ваши пользователи могут расстроиться.
В iOS Ваше приложение уничтожается сторожевым таймером, если это не реагирует на события пользовательского интерфейса для предопределенного количества времени. Тайм-ауты для большинства операций организации сети более длительны, чем тот из сторожевого таймера iOS. Таким образом, если Ваша сетевая связь прервется во время синхронного сетевого вызова, Ваше приложение, как гарантируют, будет уничтожено.
Если Ваше приложение для iOS генерирует отчет катастрофического отказа с кодом исключения 0x8badfood
, это означает, что сторожевой таймер iOS уничтожил Ваше приложение, потому что это было безразлично; такой катастрофический отказ, возможно, был вызван синхронным сетевым вызовом.
Какао (основа) и CFNetwork (базовая основа) код
Самое простое и наиболее распространенный способ выполнить сети асинхронно должны запланировать Ваш сетевой объект в цикле выполнения текущего потока.
Вся основа или CFNetwork
сети объектов — включая NSURLConnection
, NSStream
/ CFStream
, NSNetService
/ CFNetService
, и CFSocket
— имейте встроенную интеграцию цикла выполнения. Каждый из этих объектов имеет ряд методов делегата или функций обратного вызова, которые вызывают в течение сетевой связи Вашего объекта.
Если Вы хотите выполнить в вычислительном отношении интенсивную задачу (такую как обработка большого загруженного файла) в течение Вашей сетевой связи, необходимо сделать так на отдельном потоке. NSOperation
класс позволяет Вам инкапсулировать такую задачу в объекте операции, который можно легко работать на отдельном потоке путем добавления его к NSOperationQueue
объект.
Для получения дополнительной информации читайте Циклы Выполнения в Поточной обработке Руководства по программированию. Для примера кода посмотрите LinkedImageFetcher и ListAdder.
Код POSIX
Сети POSIX представляют собой некоторые уникальные проблемы, и таким образом имеют некоторые уникальные подсказки:
Создайте сокеты правильно. Надлежащее требование создания сокета TCP:
socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); // IPv4 |
socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); // IPv6 |
Можно создать сокет UDP путем замены IPPROTO_TCP
с IPPROTO_UDP
в отрывке выше и замене SOCK_STREAM
с SOCK_DGRAM
.
Если Вы используете избранный системный вызов, отслеживаете, которых фактически используются сокеты. Много плохо записанных основанных на сокете программ неправильно предполагают, что им принадлежат каждый сокет или дескриптор файла от 3 (или 0) через сокет с самым высоким номером, что они в настоящее время имеют открытый, и используют простое for
цикл для заполнения набора дескриптора файла. Это не только может привести к низкой производительности, но также и может вызвать неправильное поведение, если демон открывает файлы, которые могут закончиться с числами дескриптора файла в том диапазоне.
Вместо этого необходимо сохранить две части состояния в сетевом коде:
Целое число, отслеживающее сокет с самым высоким номером, который Вы в настоящее время имеете открытый. Необходимо обновить это (в случае необходимости) каждый раз, когда Вы открываете или закрываете новый сокет.
Полное
FD_SET
в котором соответствующий бит установлен для каждого открытого сокета. Вместо того, чтобы передать этот наборselect
системный вызов (то, которое уничтожает содержание любых наборов дескриптора, передало как параметры), необходимо скопировать набор дескриптора сFD_COPY
и передайте копиюselect
.Также, если у Вас есть массив дескрипторов файлов (или некоторый другой способ отслеживать их), можно создать новое
FD_SET
от того массива.
Используйте выполненные циклы и неблокирование I/O для управления асинхронными чтениями. Во всех кроме большинства тривиальных инструментов командной строки сети POSIX должны всегда выполняться основанным на выполнении-циклом способом с помощью любого GCD, kqueue
API, или select
системный вызов.
Избегите синхронных сетей на основном потоке. При использовании синхронных вызовов сети POSIX никогда не должны выполняться на основном потоке программы никакого приложения GUI или другого интерактивного инструмента. Это включает:
Чтение от и запись в сокеты, не установленные в неблокирование.
Подключение сокета.
Выполнение поисков DNS (особенно с
getaddrinfo
,getnameinfo
,getaddrinfo
, иgethostbyaddr
).
Вместо этого или выполните синхронные запросы к отдельному потоку или используйте API, выполняющий эти операции асинхронно, такие как GCD, kqueue
API или высокоуровневый APIs, интегрирующий с Какао выполненные циклы (CFSocket
или CFStream
, например).
Для получения дополнительной информации об опциях, доступных Вам, считайте Оценку Ваших Сетевых Потребностей и Используя Потоки Сокета и Сокеты.
Установите надлежащие тайм-ауты при использовании выбора. select
системный вызов позволяет Вам указывать промежуток времени для ожидания перед возвращением управления к коду. Значение, которое Вы выбираете для этого тайм-аута, очень важно:
Если Ваш код должен выполнить другие операции в фоновом режиме, это значение тайм-аута определяет, как часто работают те другие операции. Чем короче значение, тем более часто Ваш код может сделать другие вещи в фоновом режиме, но больше циклов CPU это использует при ожидании.
В целом, если Ваше приложение будит больше, чем несколько раз в секунду (или даже один раз в секунду на iOS), необходимо, вероятно, выполнять ту работу на отдельном потоке. Кроме того, это обычно - знак, что Вы делаете что-то не так, такое как опрос, чтобы видеть, был ли файл удален или изменен вместо того, чтобы использовать более надлежащую основанную на уведомлении технологию такой в качестве
kqueue
API.При использовании этого пробуждения для проверки, чтобы видеть, сделал ли другой поток в приложении что-то, необходимо рассмотреть использование пары подключенных сокетов вместо этого. Сделать это, сначала пара сокета с
socketpair
. Тогда добавьте один из подключенных сокетов к Вашему набору дескриптора. Когда другой поток должен сказать Ваш сетевой поток о событии, это может разбудить Ваш сетевой поток путем записи данных в другой сокет. Обязательно отслеживайте, из которых сокеты создавались таким образом так, чтобы Ваш код мог обработать данные по-другому в зависимости от того, прибыло ли это из внешнего соединения или из Вашего приложения.Если Ваш код не должен выполнять операции в фоновом режиме, необходимо передать
NULL
. ПередачаNULL
гарантирует, что Ваш код берет как можно меньше CPU при ожидании входящих соединений или данных.
Кроме того, если Вы используете тайм-ауты, знать что select
возвраты системного вызова EINTR
когда стреляет таймер. Ваш код должен быть подготовлен к этому возвращаемому значению и не должен обрабатывать его как ошибку.
Используйте сокеты POSIX эффективно (если вообще). Если Вы используете сокеты POSIX непосредственно:
Обработайте или отключите
SIGPIPE
.Когда соединение закрывается, по умолчанию, Ваш процесс получает a
SIGPIPE
сигнал. Если Ваша программа не обработает или проигнорирует этот сигнал, то Ваша программа сразу выйдет. Можно обработать это одним из двух способов:Проигнорируйте сигнал глобально со следующей строкой кода:
signal(SIGPIPE, SIG_IGN);
Скажите сокету не отправлять сигнал во-первых со следующими строками кода (заменяющий переменной, содержащей Ваш сокет вместо
sock
):int value = 1;
setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &value, sizeof(value));
Для максимальной совместимости необходимо установить этот флаг на каждом входящем сокете сразу после вызова
accept
в дополнение к установке флага на самом сокете слушания.
Используйте сокеты неблокирования, если это возможно.
Сохраните сокет, отправляют буфер, полный по мере возможности.
Обработайте входящие данные рано, и часто сохранить сокет получают пустой буфер.
Поддерживайте и IPv4 и IPv6.
Проверьте возвращаемые значения от чтений сокета и записей.
Будьте подготовлены обработать
EAGAIN
иEWOULDBLOCK
ошибки, указывающие, что никакие данные не доступны при чтении, или что никакое пространство буфера вывода не доступно при записи. Эти ошибки являются нормальными, нефатальными ошибками; Ваша программа не должна закрывать соединение, когда это получает их.
Для примера кода POSIX, соблюдающего эти правила, посмотрите Запись Основанного на TCP Сервера в Сетях Тем Программирования.
Избегите разрешать имена DNS прежде, чем соединиться с узлом
Предпочтительный способ соединиться с узлом с API, принимающим имя DNS, такой как CFHost
или CFNetService
.
Несмотря на то, что Ваша программа может разрешить имя DNS и затем соединиться с получающимся IP-адресом, необходимо обычно избегать делать так. Поиски DNS обычно возвращают многократные IP-адреса, и (на прикладном уровне) это не всегда очевидно, какой IP-адрес является лучшим для соединения с удаленным узлом. Например:
Наиболее современные компьютеры и другие мобильные устройства являются многосетевыми. Это означает, что они существуют больше чем в одной физической сети одновременно. Ваш компьютер мог бы быть на Wi-Fi и Ethernet; Ваш сотовый телефон мог бы быть на Wi-Fi и 3G; и т.д. Однако не все узлы обязательно доступны по каждому соединению.
Например, Удаленное приложение на Вашем iPhone позволяет Вам управлять своим Apple TV, но только по соединению Wi-Fi. Эти два устройства не могут связаться друг с другом по сотовому соединению Вашего телефона, потому что Ваш Apple TV не имеет никакого общедоступного IP-адреса.
Если и Ваше устройство и сервер, который Вы подключаете для имения многократных IP-адресов в различных сетях, лучшего IP-адреса для соединения с сервером, могут зависеть, на котором объединяются в сеть, соединение пройдет.
Например, если Ваш домашний медиасервер имеет один IP-адрес на Вашем соединенном проводом LAN и второй IP-адрес на сети Wi-Fi, операционная система может часто обнаруживать различие в производительности и одобрять более быстрый, основанный на LAN IP-адрес. В отличие от этого, если Ваша программа ищет IP-адрес, она имеет приблизительно равную возможность соединения с любым IP-адресом.
Если сервер имеет и IPv4 и протоколы IPv6, это может не быть достижимое использование обоих протоколов. При помощи основанного на имени хоста API операционная система может попробовать и одновременно и использовать первую, соединяющуюся успешно. Если Ваша программа ищет IP-адрес, она не могла бы соединиться успешно, в зависимости от которого адреса она выбрала.
Если сервер DNS является более старым сервером, не обрабатывающим IPv6, и Вы запрашиваете
AAAA
(Адрес IPv6) рекордные, синхронные запросы DNS блокируют, пока запрос не испытывает таймаут (30 секунд по умолчанию). Если Вы используете API, берущий имя хоста, операционная система скрывает эту проблему от Вас путем выпуска IPv4 и запросов IPv6 параллельно, и затем отмены выдающегося поиска IPv6, как только соединение IPv4 установлено успешно.Если сервер будет использовать Добрый день для распространения службы по локальному для ссылки IP-адресу (потому что DHCP не работает над сетью по некоторым причинам), если приложение будет искать ту службу и будет разрешать его к IP-адресу, то получающийся адрес будет работать только, пока устройство сохраняет тот IP-адрес. Если сервер DHCP прибывает онлайн, IP-адрес может измениться, в котором времени Ваша программа больше не будет в состоянии соединиться со старым адресом.
При соединении использования распространенного имени вместо этого, программа будет продолжать быть в состоянии соединиться даже после того, как сервер наконец получит IP-адрес (пока компьютер или устройство, выполняющее ту программу, получают обновленную рекламу).
Если сервер имеет с другой стороны по требованию VPN, становящийся доступным только, когда пользователь пытается получить доступ к узлу whitelisted, соединяющийся IP не активирует тот VPN, что означает, что узел никогда не будет становиться достижимым.
Если Вы не можете избежать разрешать, что DNS называет себя, сначала проверяет, чтобы видеть ли CFHost
API выполняет Ваши требования; это обеспечивает список адресов для данного узла вместо просто единственного адреса. Если CFHost
API не удовлетворяет Ваши потребности, использует Открытие Службы DNS API.
Для получения дополнительной информации читайте Сетевые Понятия, Пишущий Основанный на TCP Сервер в Сетях Тем Программирования, Ссылки CFHost и Ссылки Открытия C Службы DNS. Для примера кода см. SRVResolver.
Поймите переход IPv6
В настоящее время преобладающая версия Протокола Интернета (IPv4) исчерпала уникальные адреса. Для решения этой проблемы Версия 6 (IPv6) Протокола Интернета постоянно заменяет IPv4. В зависимости от природы Вашей программы этот переход имеет различные импликации:
Если Вы пишете программу клиентской стороны с помощью высокого уровня, объединяющего APIS В СЕТЬ, и Вы соединяетесь по имени, Вы не должны должны быть изменять что-либо в своей программе для него для работы с адресами IPv6. (Если Вы не соединяетесь по имени, вероятно, необходимо быть. Посмотрите Избегают Разрешать Имена DNS прежде, чем Соединиться с Узлом.)
Если Вы пишете программе серверной стороны или другому низкому уровню сетевую программу, необходимо удостовериться, что код сокета работает правильно и с IPv4 и с адресами IPv6. Проект примера кода RemoteCurrency иллюстрирует, как сделать это.
Не используйте NSSocketPort (OS X) или NSFileHandle для общей связи с сокетом
Несмотря на его имя, NSSocketPort
класс (доступный только в OS X) не предназначается для общей сетевой связи. NSSocketPort
класс является частью распределенной системы объектов Какао, предназначающейся для управляемой коммуникации между приложениями Какао на единственной машине или в локальной сети. Для получения дополнительной информации о распределенной системе объектов посмотрите Распределенные Объекты Программировать Темы.
Точно так же NSFileHandle
класс не разработан для общих сетей. NSFileHandle
класс обходит стандартный сетевой стек, переносящий следующие недостатки:
Сетевые соединения, сделанные с
NSFileHandle
может быть значительно менее эффективным, чем сделанные со стандартом, объединяющим APIS В СЕТЬ.Исторически, использование
NSFileHandle
поскольку сети привели или к чрезвычайно низкой производительности или к странным, твердым к отладке отказам.Нет никакого прямого способа использовать аутентификацию TLS и шифрование на соединениях, сделанных с
NSFileHandle
.В iOS,
NSFileHandle
автоматически не активирует сотовый модем устройства или по требованию VPN.
Вместо этого используйте NSStream
для удаленных соединений и CFSocket
для слушания. Для получения дополнительной информации посмотрите Запись Основанного на TCP Сервера в Сетях Тем Программирования.