UNIX: разработка сетевых приложений
9 err_quit("usage: tcpcli <IPaddress>");10 sockfd = Socket(AF_INET, SOCK_STREAM, 0);11 bzero(&servaddr, sizeof(servaddr));12 servaddr.sin_family = AF_INET;13 servaddr.sin_port = htons(SERV_PORT);14 Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);15 Connect(sockfd, (SA*)&servaddr, sizeof(servaddr));16 ling.l_onoff = 1; /* для отправки сегмента RST при закрытии соединения */17 ling.l_linger = 0;18 Setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));19 Close(sockfd);20 exit(0);21 }Установка параметра сокета SO_LINGERКак только соединение устанавливается, мы задаем параметр сокета16-19, устанавливая флагSO_LINGERв единицу и обнуляя времяl_onoff. Как утверждалось в разделе 7.5, это вызывает отправку RST на сокете TCP при закрытии соединения. Затем с помощью функцииl_lingerмы закрываем сокет.closeПотом мы изменяем наш сервер TCP, приведенный в листингах 6.3 и 6.4, с тем чтобы после сообщения функции
о готовности прослушиваемого сокета для чтения, но перед вызовом функцииselectнаступала пауза. В следующем коде, взятом из начала листинга 6.4, две добавленные строки помечены знакомaccept.+if (FD_ISSET(listenfd, &rset)) { /* новое соединение */+ printf("listening socket readable\n");+ sleep(5);clilen = sizeof(cliaddr);connfd = Accept(listenfd, (SA*)&cliaddr, &clilen);Здесь мы имитируем занятый сервер, который не может вызвать функцию
сразу же, как только функцияacceptсообщит, что прослушиваемый сокет готов для чтения. Обычно подобное замедление со стороны сервера не вызывает проблем (на самом деле именно для этих ситуаций предусмотрена очередь полностью установленных соединений). Но поскольку после установления соединения от клиента прибыл сегмент RST, у нас возникает проблема.selectВ разделе 5.11 мы отмечали, что когда клиент разрывает соединение до того, как сервер вызывает функцию
, в Беркли-реализациях прерванное соединение не возвращается серверу, в то время как другие реализации должны возвращать ошибкуaccept, но часто вместо нее возвращают ошибкуECONNABORTED. Рассмотрим Беркли-реализацию.EPROTO■ Клиент устанавливает соединение и затем прерывает его, как показано в листинге 16.14.
■ Функция
сообщает процессу сервера, что дескриптор готов для чтения, но у сервера вызов функцииselectзанимает некоторое, хотя и непродолжительное, время.accept■ После того, как сервер получил сообщение от функции
, и прежде, чем была вызвана функцияselect, прибыл сегмент RST от клиента.accept■ Установленное соединение удаляется из очереди, и мы предполагаем, что не существует никаких других установленных соединений.
■ Сервер вызывает функцию
, но поскольку установленных соединений нет, он оказывается заблокирован.acceptСервер останется блокированным в вызове функции
до тех пор, пока какой-нибудь другой клиент не установит с ним соединение. Но если сервер аналогичен показанному в листинге 6.4, в это время он заблокирован в вызове функцииacceptи не может обрабатывать никакие другие готовые дескрипторы.acceptПРИМЕЧАНИЕПроблема в некоторой степени аналогична проблеме, называемой атакой типа «отказ в обслуживании», описанной в разделе 6.8. Однако в данном случае сервер выходит из состояния блокировки, как только другой клиент установит соединение.
Чтобы решить эту проблему, нужно соблюдать два следующих правила:
1. Всегда делать прослушиваемый сокет неблокируемым, если мы используем функцию
для определения того, готово ли соединение к обработке функцией accept.select2. Игнорировать следующие ошибки, возникающие при повторном вызове функции
:accept(для Беркли-реализаций, когда клиент разрывает соединение),EWOULDBLOCK(для реализаций POSIX, когда клиент разрывает соединение),ECONNABORTED(для реализаций SVR4, когда клиент разрывает соединение) иEPROTO(если перехватываются сигналы).EINTR16.7. Резюме
В примере неблокируемого чтения и записи в разделе 16.2 использовался наш клиент
, который мы изменили для применения неблокируемого ввода-вывода на соединении TCP с сервером. Функцияstr_cliобычно используется с неблокируемым вводом-выводом для определения того момента, когда дескриптор станет готов для чтения или записи. Эта версия нашего клиента является самой быстродействующей из всех показанных версией, хотя требует нетривиального изменения кода. Затем мы показали, что проще разделить процесс клиента на две части при помощи функцииselect. Мы используем ту же технологию при создании потоков в листинге 26.1.forkНеблокируемая функция
позволяет нам во время трехэтапного рукопожатия TCP выполнять другие задачи вместо блокирования в вызове функцииconnect. К сожалению, с этими функциями также связана проблема совместимости, так как различные реализации по-разному указывают, успешно ли установлено соединение или произошла ошибка. Мы использовали неблокируемые соединения для создания нового клиента, аналогичного веб-клиенту, открывающему одновременно множество соединений TCP для уменьшения затрат времени при получении нескольких файлов от сервера. Подобное инициирование множества соединений может сократить временные затраты, но также является «недружественным по отношению к сети», поскольку не позволяет воспользоваться алгоритмом TCP, предназначенным для предотвращения перегрузки (congestion avoidance).connectУпражнения
1. Обсуждая листинг 16.6, мы отметили, что родительский процесс должен вызвать функцию
, а не функциюshutdown. Почему?close