Страница 50 из 399
5.6. Нормальный запуск
Наш небольшой пример использования TCP (около 150 строк кода для двух функций main, str_echo, str_cli, readline и writen) позволяет понять, как запускаются и завершаются клиент и сервер и, что наиболее важно, как развиваются события, если произошел сбой на узле клиента или в клиентском процессе, потеряна связь в сети и т.д. Только при понимании этих «граничных условий» и их взаимодействия с протоколами TCP/IP мы сможем обеспечить устойчивость клиентов и серверов, которые смогут справляться с подобными ситуациями.
Сначала мы запускаем сервер в фоновом режиме на узле linux.
linux % <b>tcpserv01 &</b>
[1] 17870
Когда сервер запускается, он вызывает функции socket, bind, listen и accept, а затем блокируется в вызове функции accept. (Мы еще не запустили клиент.) Перед тем, как запустить клиент, мы запускаем программу netstat, чтобы проверить состояние прослушиваемого сокета сервера.
linux % <b>netstat -a</b>
Active Internet co
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 *:9877 *:* LISTEN
Здесь мы показываем только первую строку вывода и интересующую нас строку. Эта команда показывает состояние всех сокетов в системе, поэтому вывод может быть большим. Для просмотра прослушиваемых сокетов следует указать параметр -a.
Результат совпадает с нашими ожиданиями. Сокет находится в состоянии LISTEN, локальный IP-адрес задан с помощью символа подстановки (то есть является универсальным) и указан локальный порт 9877. Функция netstat выводит звездочку для нулевого IP-адреса (INADDR_ANY, универсальный адрес) или для нулевого порта.
Затем на том же узле мы запускаем клиент, задав IP-адрес сервера 127.0.0.1. Мы могли бы задать здесь и нормальный адрес сервера (его IP-адрес в сети).
linux % <b>tcpcli01 127.0.0.1</b>
Клиент вызывает функции socket и co
1. Клиент вызывает функцию str_cli, которая блокируется в вызове функции fgets, поскольку мы еще ничего не ввели.
2. Когда функция accept возвращает управление процессу-серверу, последний вызывает функцию fork, а дочерний процесс вызывает функцию str_echo. Та вызывает функцию read, блокируемую в ожидании получения данных от клиента.
3. Родительский процесс сервера снова вызывает функцию accept и блокируется в ожидании подключения следующего клиента.
У нас имеется три процесса, и все они находятся в состоянии ожидания (блокированы): клиент, родительский процесс сервера и дочерний процесс сервера.
Мы специально поставили первым пунктом (после завершения трехэтапного рукопожатия) вызов функции str_cli, происходящий на стороне клиента, а затем уже перечислили действия на стороне сервера. Причину объясняет рис. 2.5: функция co
Мы намеренно запускаем и клиент, и сервер на одном узле — так проще всего экспериментировать с клиент-серверными приложениями. Поскольку клиент и сервер запущены на одном узле, функция netstat отображает теперь две дополнительные строки вывода, соответствующие соединению TCP:
linux % <b>netstat -a</b>
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 localhost:9877 localhost:42758 ESTABLISHED
tcp 0 0 localhost:42758 localhost:42758 ESTABLISHED
tcp 0 0 *:9877 *:* LISTEN
Первая из строк состояния ESTABLISHED соответствует дочернему сокету сервера, поскольку локальным портом является порт 9877. Вторая строка ESTABLISHED — это клиентский сокет, поскольку локальный порт — порт 42 758. Если мы запускаем клиент и сервер на разных узлах, на узле клиента будет отображаться только клиентский сокет, а на узле сервера — два серверных сокета.
Для проверки состояний процессов и отношений между ними можно также использовать команду ps:
linux % <b>ps -t pts/6 -o pid,ppid,tty,stat,args,wchan</b>
PID PPID TT STAT COMMAND WCHAN
22038 22036 pts/6 S -bash wait4
17870 22038 pts/6 S ./tcpserv01 wait_for_co
19315 17870 pts/6 S ./tcpserv01 tcp_data_wait
19314 22038 pts/6 S ./tcpcli01 127.0.0.1 read_chan
Мы вызвали ps с несколько необычным набором аргументов для того, чтобы получить всю необходимую для дальнейшего обсуждения информацию. Мы запустили клиент и сервер из одного окна (pts/6, что означает псевдотерминал 6). В колонках PID и PPID показаны отношения между родительским и дочерним процессами. Можно точно сказать, что первая строка tcpserv01 соответствует родительскому процессу, а вторая строка tcpserv01 — дочернему, поскольку PPID дочернего процесса — это PID родительского. Кроме того, PPID родительского процесса совпадает с PID интерпретатора команд (bash).
Колонка STAT для всех трех сетевых процессов отмечена символом S. Это означает, что процессы находятся в состоянии ожидания (sleeping). Если процесс находится в состоянии ожидания, колонка WCHAN сообщит нам о том, чем он занят. В Linux значение wait_for_co