Книга: UNIX: разработка сетевых приложений
5.12. Завершение процесса сервера
5.12. Завершение процесса сервера
Теперь мы запустим соединение клиент-сервер и уничтожим дочерний процесс сервера. Это симулирует сбой процесса сервера, благодаря чему мы сможем выяснить, что происходит с клиентом в подобных ситуациях. (Следует точно различать сбой процесса сервера, который мы рассмотрим здесь, и сбой на самом узле сервера, о котором речь пойдет в разделе 5.14.) События развиваются так:
1. Мы запускаем сервер и клиент на разных узлах и вводим на стороне клиента одну строку, чтобы проверить, все ли в порядке. Строка отражается дочерним процессом сервера.
2. Мы находим идентификатор дочернего процесса сервера и уничтожаем его с помощью программы kill
. Одним из этапов завершения процесса является закрытие всех открытых дескрипторов в дочернем процессе. Это вызывает отправку сегмента FIN клиенту, и TCP клиента отвечает сегментом ACK. Это первая половина завершения соединения TCP.
3. Родительскому процессу сервера посылается сигнал SIGCHLD
, и он корректно обрабатывается (см. листинг 5.9).
4. С клиентом ничего не происходит. TCP клиента получает от TCP сервера сегмент FIN и отвечает сегментом ACK, но проблема состоит в том, что клиентский процесс блокирован в вызове функции fgets
в ожидании строки от терминала.
5. Запуск программы netstat
на этом шаге из другого окна на стороне клиента показывает состояние клиентского сокета:
linux % netstat -a | grep 9877
tcp 0 0 *:9877 *:* LISTEN
tcp 0 0 localhost:9877 localhost:9877 FIN_WAIT2
tcp 1 0 localhost.43604 localhost:9877 CLOSE_WAIT
Как видите, согласно рис. 2.4, осуществилась половина последовательности завершения соединения TCP.
6. Мы можем снова ввести строку на стороне клиента. Вот что происходит на стороне клиента (начиная с шага 1):
linux % tcpcli01 127.0.0.1 запускаем клиент
hello первая строка, которую мы ввели
hello она корректно отражается
теперь мы уничтожаем (kill) дочерний процесс
сервера на узле сервера
another line затем мы вводим следующую строку на стороне клиента
str_cli: server terminated prematurely
Когда мы вводим следующую строку, функция str_cli
вызывает функцию writen
, и TCP клиента отправляет данные серверу. TCP это допускает, поскольку получение сегмента FIN протоколом TCP клиента указывает только на то, что процесс сервера закрыл свой конец соединения и больше не будет отправлять данные. Получение сегмента FIN не сообщает протоколу TCP клиента, что процесс сервера завершился (хотя в данном случае он завершился). Мы вернемся к этому вопросу в разделе 6.6, когда будем говорить о половинном закрытии TCP.
Когда TCP сервера получает данные от клиента, он отвечает сегментом RST, поскольку процесс, у которого был открытый сокет, завершился. Мы можем проверить, что этот сегмент RST отправлен, просмотрев пакеты с помощью программы tcpdump
.
7. Однако процесс клиента не увидит сегмента RST, поскольку он вызывает функцию readline
сразу же после вызова функции writen
, и readline
сразу же возвращает 0 (признак конца файла) по причине того, что на шаге 2 был получен сегмент FIN. Наш клиент не предполагает получать признак конца файла на этом этапе (см. листинг 5.3), поэтому он завершает работу, сообщая об ошибке Server terminated prematurely
(Сервер завершил работу преждевременно).
ПРИМЕЧАНИЕ
Этапы описанной последовательности также зависят от синхронизации времени. Вызов readline на стороне клиента может произойти до получения им пакета RST от сервера, но может произойти и после. Если readline вызывается до получения RST, происходит то, что мы описали выше (клиент считывает символ конца файла). Если же первым будет получен пакет RST, функция readline возвратит ошибку ECONNRESET (соединение сброшено собеседником).
8. Когда клиент завершает работу (вызывая функцию err_quit
в листинге 5.4), все его открытые дескрипторы закрываются.
Проблема заключается в том, что клиент блокируется в вызове функции fgets
, когда сегмент FIN приходит на сокет. Клиент в действительности работает с двумя дескрипторами — дескриптором сокета и дескриптором ввода пользователя, и поэтому он должен блокироваться при вводе из любого источника (сейчас в функции str_cli
он блокируется при вводе только из одного источника). Обеспечить подобное блокирование — это одно из назначений функций select
и poll
, о которых рассказывается в главе 6. Когда в разделе 6.4 мы перепишем функцию str_cli
, то как только мы уничтожим с помощью программы kill
дочерний процесс сервера, клиенту будет отправлено уведомление о полученном сегменте FIN.
- 5.1. Введение
- 5.2. Эхо-сервер TCP: функция main
- 5.3. Эхо-сервер TCP: функция str_echo
- 5.4. Эхо-клиент TCP: функция main
- 5.5. Эхо-клиент TCP: функция str_cli
- 5.6. Нормальный запуск
- 5.7. Нормальное завершение
- 5.8. Обработка сигналов POSIX
- 5.9. Обработка сигнала SIGCHLD
- 5.10. Функции wait и waitpid
- 5.11. Прерывание соединения перед завершением функции accept
- 5.12. Завершение процесса сервера
- 5.13. Сигнал SIGPIPE
- 5.14. Сбой на узле сервера
- 5.15. Сбой и перезагрузка на узле сервера
- 5.16. Выключение узла сервера
- 5.17. Итоговый пример TCP
- 5.18. Формат данных
- 5.19. Резюме
- Упражнения
- Досрочное завершение сервера
- 9.1.5. Завершение процесса
- Доступ к WMI с помощью сервера сценариев
- Создание, завершение и просмотр учетной записи процесса
- Стандартные сценарии сервера сценариев
- 16.7. Досрочное завершение сервера или клиента
- Досрочное завершение клиента
- 5.16. Выключение узла сервера
- 5.7. Нормальное завершение
- 5.14. Сбой на узле сервера
- Запуск InterBase-сервера
- Расширенная установка InterBase-сервера