Предотвращение общих сетевых ошибок

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

Очистите свои соединения

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

Рекомендуемый способ включить проверку активности TCP путем установки SO_KEEPALIVE флаг на сокете с setsockopt.

Избегите Сокетов POSIX и CFSocket на iOS, Где Возможно

Используя сокеты POSIX непосредственно имеет и преимущества и недостатки относительно использования их через высокоуровневый API. Основные преимущества:

Основные недостатки:

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

Если Вы не можете избежать разрешать, что DNS называет себя, сначала проверяет, чтобы видеть ли CFHost API выполняет Ваши требования; это обеспечивает список адресов для данного узла вместо просто единственного адреса. Если CFHost API не удовлетворяет Ваши потребности, использует Открытие Службы DNS API.

Для получения дополнительной информации читайте Сетевые Понятия, Пишущий Основанный на TCP Сервер в Сетях Тем Программирования, Ссылки CFHost и Ссылки Открытия C Службы DNS. Для примера кода см. SRVResolver.

Поймите переход IPv6

В настоящее время преобладающая версия Протокола Интернета (IPv4) исчерпала уникальные адреса. Для решения этой проблемы Версия 6 (IPv6) Протокола Интернета постоянно заменяет IPv4. В зависимости от природы Вашей программы этот переход имеет различные импликации:

Не используйте NSSocketPort (OS X) или NSFileHandle для общей связи с сокетом

Несмотря на его имя, NSSocketPort класс (доступный только в OS X) не предназначается для общей сетевой связи. NSSocketPort класс является частью распределенной системы объектов Какао, предназначающейся для управляемой коммуникации между приложениями Какао на единственной машине или в локальной сети. Для получения дополнительной информации о распределенной системе объектов посмотрите Распределенные Объекты Программировать Темы.

Точно так же NSFileHandle класс не разработан для общих сетей. NSFileHandle класс обходит стандартный сетевой стек, переносящий следующие недостатки:

Вместо этого используйте NSStream для удаленных соединений и CFSocket для слушания. Для получения дополнительной информации посмотрите Запись Основанного на TCP Сервера в Сетях Тем Программирования.