UNIX: разработка сетевых приложений - Страница 56
27 Close(listenfd); /* закрываем прослушиваемый сокет */28 str_echo(connfd); /* обрабатываем запрос */29 exit(0);30 }31 Close(connfd); /* родитель закрывает присоединенный сокет */32 }33 }Целью этого раздела было продемонстрировать три сценария, которые могут встретиться в сетевом программировании.
1. При выполнении функции
forkSIGCHLD2. При перехватывании сигналов мы должны обрабатывать прерванные системные вызовы.
3. Обработчик сигналов
SIGCHLDwaitpidОкончательная версия нашего сервера TCP (см. листинг 5.9) вместе с обработчиком сигналов
SIGCHLD5.11. Прерывание соединения перед завершением функции accept
Существует другое условие, аналогичное прерванному системному вызову, пример которого был описан в предыдущем разделе. Оно может привести к возвращению функцией
acceptaccept
Рис. 5.4. Получение сегмента RST для состояния соединения ESTABLISHED перед вызовом функции accept
Трехэтапное рукопожатие TCP завершается, устанавливается соединение, а затем TCP клиента посылает сегмент RST. На стороне сервера соединение ставится в очередь в ожидании вызова функции
acceptacceptК сожалению, принцип обработки прерванного соединения зависит от реализации. Реализации, происходящие от Беркли, обрабатывают прерванное соединение полностью внутри ядра, и сервер никогда не узнает об этом. Большинство реализаций SVR4, однако, возвращают процессу ошибку, и эта ошибка зависит от реализации. При этом переменная errno принимает значение
EPROTOECONNABORTEDEPROTOacceptECONNABORTEDЭтот сценарий очень просто имитировать. Запустите сервер, который должен вызвать функции socket, bind и listen, а затем перед вызовом функции accept переведите сервер на короткое время в состояние ожидания. Пока процесс сервера находится в состоянии ожидания, запустите клиент, который вызовет функции socket и connect. Как только функция connect завершится, установите параметр сокета SO_LINGER, чтобы сгенерировать сегмент RST (который мы описываем в разделе 7.5 и демонстрируем в листинге 16.14), и завершите процессы.
В [128] описана обработка этой ошибки в Беркли-ядрах (Berkeley-derived kernels), которые никогда не передают ее процессу. Обработка RST с вызовом функции tcp_close представлена в [128, с. 964]. Эта функция вызывает функцию in_pcbdetach [128, с. 897], которая, в свою очередь, вызывает функцию sofree [128, с. 719]. Функция sofree [128, с. 473] обнаруживает, что сокет все еще находится в очереди полностью установленных соединений прослушиваемого сокета. Она удаляет этот сокет из очереди и освобождает сокет. Когда сервер, наконец, вызовет функцию accept, он не сможет узнать, что установленное соединение было удалено из очереди.
Мы вернемся к подобным прерванным соединениям в разделе 16.6 и покажем, какие проблемы они могут порождать совместно с функцией
select5.12. Завершение процесса сервера
Теперь мы запустим соединение клиент-сервер и уничтожим дочерний процесс сервера. Это симулирует сбой процесса сервера, благодаря чему мы сможем выяснить, что происходит с клиентом в подобных ситуациях. (Следует точно различать сбой процесса сервера, который мы рассмотрим здесь, и сбой на самом узле сервера, о котором речь пойдет в разделе 5.14.) События развиваются так:
1. Мы запускаем сервер и клиент на разных узлах и вводим на стороне клиента одну строку, чтобы проверить, все ли в порядке. Строка отражается дочерним процессом сервера.
2. Мы находим идентификатор дочернего процесса сервера и уничтожаем его с помощью программы
kill3. Родительскому процессу сервера посылается сигнал
SIGCHLD4. С клиентом ничего не происходит. TCP клиента получает от TCP сервера сегмент FIN и отвечает сегментом ACK, но проблема состоит в том, что клиентский процесс блокирован в вызове функции
fgets5. Запуск программы
netstatlinux % netstat -a | grep 9877tcp 0 0 *:9877 *:* LISTENtcp 0 0 localhost:9877 localhost:9877 FIN_WAIT2tcp 1 0 localhost.43604 localhost:9877 CLOSE_WAITКак видите, согласно рис. 2.4, осуществилась половина последовательности завершения соединения TCP.
6. Мы можем снова ввести строку на стороне клиента. Вот что происходит на стороне клиента (начиная с шага 1):
linux % tcpcli01 127.0.0.1 запускаем клиентhello первая строка, которую мы ввелиhello она корректно отражается теперь мы уничтожаем (kill) дочерний процесс сервера на узле сервераanother line затем мы вводим следующую строку на стороне клиента