UNIX: разработка сетевых приложений
Обработка тайм-аутовЕсли функция25-28возвращает нуль, это означает, что время таймера истекло, и мы возвращаем вызывающему процессу ошибкуselect. Мы также закрываем сокет, чтобы трехэтапное рукопожатие не продолжалось.ETIMEDOUTПроверка возможности чтения или записиЕсли дескриптор готов для чтения или для записи, мы вызываем функцию29-34, чтобы получить ошибку сокета (getsockopt), ожидающую обработки. Если соединение завершилось успешно, это значение будет нулевым. Если при установлении соединения произошла ошибка, это значение является значением переменнойSO_ERROR, соответствующей ошибке соединения (например,errno,ECONNREFUSEDи т.д.). Мы также сталкиваемся с нашей первой проблемой переносимости. Если происходит ошибка, Беркли-реализации функцииETIMEDOUTвозвращают нуль, а ошибка, ожидающая обработки, возвращается в нашей переменнойgetsockopt. Но в системе Solaris сама функцияerrorвозвращает -1, а переменнаяgetsockoptпри этом принимает значение, соответствующее ошибке, ожидающей обработки. В нашем коде обрабатываются оба сценария.errnoВосстановление возможности блокировки сокета и завершениеМы восстанавливаем флаги, задающие статус файла, и возвращаемся. Если наша переменная errno имеет ненулевое значение в результате выполнения функции36-42, это значение хранится в переменнойgetsockopt, и функция возвращает -1.errnoКак мы сказали ранее, проблемы переносимости для функции
связаны с различными реализациями сокетов и отключения блокировки. Во-первых, возможно, что установление соединения завершится и придут данные для собеседника до того, как будет вызвана функцияconnect. В этом случае сокет будет готов для чтения и для записи при успешном выполнении функции, как и при неудачном установленном соединении. В нашем коде, показанном в листинге 16.7, этот сценарий обрабатывается при помощи вызова функцииselectи проверки на наличие ошибки, ожидающей обработки, для сокета.getsockoptВо-вторых, проблема в том, как определить, успешно завершилось установление соединения или нет, если мы не можем считать возможность записи единственным указанием на успешное установление соединения. В Usenet предлагалось множество решений этой проблемы, которые заменяют наш вызов функции
в листинге 16.7:getsockopt1. Вызвать функцию
вместо функцииgetpeername. Если этот вызов окажется неудачным и возвратится ошибкаgetsockopt, значит, соединение не было установлено, и чтобы получить ошибку, ожидающую обработки, следует вызвать для сокета функциюENOTCONNсgetsockopt.SO_ERROR2. Вызвать функцию
с нулевым значением аргументаread. Если выполнение функции read окажется неудачным, функция connect выполнилась неудачно, и переменная errno из функцииlengthпри этом указывает на причину неудачной попытки установления соединения. Если соединение успешно установлено, функцияreadвозвращает нуль.read3. Снова вызвать функцию
. Этот вызов окажется неудачным, и если ошибка —connect, сокет уже присоединен, а значит, первое соединение завершилось успешно.EISCONNК сожалению, неблокируемая функция
— это одна из самых сложных областей сетевого программирования с точки зрения переносимости. Будьте готовы к проблемам совместимости, особенно с более ранними реализациями. Более простой технологией является создание потока (см. главу 26) для обработки соединения.connectПрерванная функция connect
Что происходит, если наш вызов функции
на обычном блокируемом сокете прерывается, скажем, перехваченным сигналом, прежде чем завершится трехэтапное рукопожатие TCP? Если предположить, что функцияconnectне перезапускается автоматически, то она возвращает ошибкуconnect. Но мы не можем снова вызвать функцию connect, чтобы добиться завершения установления соединения. Это приведет к ошибкеEINTR.EADDRINUSEВсе, что требуется сделать в этом сценарии, — вызвать функцию
, так, как мы делали в этом разделе для неблокируемой функцииselect. Тогда функцияconnectзавершится, если соединение успешно устанавливается (делая сокет доступным для записи) или если попытка соединения неудачна (сокет становится доступен для чтения и для записи).select16.5. Неблокируемая функция connect: веб-клиент
Первое практическое использование неблокируемой функции
относится к веб-клиенту Netscape (см. раздел 13.4 [112]). Клиент устанавливает соединение HTTP с веб-сервером и попадает на домашнюю страницу. На этой странице часто присутствуют ссылки на другие веб-страницы. Вместо того чтобы получать последовательно по одной странице за один раз, клиент может получить сразу несколько страниц, используя неблокируемые функцииconnect. На рис. 16.5 показан пример установления множества параллельных соединений. Сценарий, изображенный слева, показывает все три соединения, устанавливаемые одно за другим. Мы считаем, что первое соединение занимает 10 единиц времени, второе — 15, а третье — 4, что в сумме дает 29 единиц времени.connectРис. 16.5. Установление множества параллельных соединений
В центре рисунка показан сценарий, при котором мы выполняем два параллельных соединения. В момент времени 0 запускаются первые два соединения, а когда первое из них устанавливается, мы запускаем третье. Общее время сократилось почти вдвое и равно 15, а не 29 единицам времени, но учтите, что это идеальный случай. Если параллельные соединения совместно используют общий канал связи (допустим, клиент использует модем для соединения с Интернетом), то каждое из этих соединений конкурирует с другими за обладание ограниченными ресурсами этого канала связи, и время установления каждого соединения может возрасти. Например, время 10 может дойти до 15, 15 — до 20, а время 4 может превратиться в 6. Тем не менее общее время будет равно 21 единице, то есть все равно меньше, чем в последовательном сценарии.
В третьем сценарии мы выполняем три параллельных соединения и снова считаем, что эти три соединения не мешают друг другу (идеальный случай). Но общее время при этом такое же (15 единиц), как и во втором сценарии.
При работе с веб-клиентами первое соединение устанавливается само по себе, за ним следуют соединения по ссылкам, обнаруженным в данных от первого соединения. Мы показываем это на рис. 16.6.
