UNIX: разработка сетевых приложений
5.6. Нормальный запуск
Наш небольшой пример использования TCP (около 150 строк кода для двух функций
,main,str_echo,str_cliиreadline) позволяет понять, как запускаются и завершаются клиент и сервер и, что наиболее важно, как развиваются события, если произошел сбой на узле клиента или в клиентском процессе, потеряна связь в сети и т.д. Только при понимании этих «граничных условий» и их взаимодействия с протоколами TCP/IP мы сможем обеспечить устойчивость клиентов и серверов, которые смогут справляться с подобными ситуациями.writenСначала мы запускаем сервер в фоновом режиме на узле
.linuxlinux % <b>tcpserv01 &</b>[1] 17870Когда сервер запускается, он вызывает функции
,socket,bindиlisten, а затем блокируется в вызове функцииaccept. (Мы еще не запустили клиент.) Перед тем, как запустить клиент, мы запускаем программуaccept, чтобы проверить состояние прослушиваемого сокета сервера.netstatlinux % <b>netstat -a</b>Active Internet connections (servers and established)Proto Recv-Q Send-Q Local Address Foreign Address Statetcp 0 0 *:9877 *:* LISTENЗдесь мы показываем только первую строку вывода и интересующую нас строку. Эта команда показывает состояние всех сокетов в системе, поэтому вывод может быть большим. Для просмотра прослушиваемых сокетов следует указать параметр
.-aРезультат совпадает с нашими ожиданиями. Сокет находится в состоянии LISTEN, локальный IP-адрес задан с помощью символа подстановки (то есть является универсальным) и указан локальный порт 9877. Функция
выводит звездочку для нулевого IP-адреса (netstat, универсальный адрес) или для нулевого порта.INADDR_ANYЗатем на том же узле мы запускаем клиент, задав IP-адрес сервера 127.0.0.1. Мы могли бы задать здесь и нормальный адрес сервера (его IP-адрес в сети).
linux % <b>tcpcli01 127.0.0.1</b>Клиент вызывает функции
иsocket, последняя осуществляет трехэтапное рукопожатие TCP. Когда рукопожатие TCP завершается, функция connect возвращает управление процессу-клиенту, а функцияconnect— процессу-серверу. Соединение установлено. Затем выполняются следующие шаги:accept1. Клиент вызывает функцию
, которая блокируется в вызове функцииstr_cli, поскольку мы еще ничего не ввели.fgets2. Когда функция
возвращает управление процессу-серверу, последний вызывает функциюaccept, а дочерний процесс вызывает функциюfork. Та вызывает функциюstr_echo, блокируемую в ожидании получения данных от клиента.read3. Родительский процесс сервера снова вызывает функцию
и блокируется в ожидании подключения следующего клиента.acceptУ нас имеется три процесса, и все они находятся в состоянии ожидания (блокированы): клиент, родительский процесс сервера и дочерний процесс сервера.
ПРИМЕЧАНИЕМы специально поставили первым пунктом (после завершения трехэтапного рукопожатия) вызов функции str_cli, происходящий на стороне клиента, а затем уже перечислили действия на стороне сервера. Причину объясняет рис. 2.5: функция connect возвращает управление, когда клиент получает второй сегмент рукопожатия. Однако функция accept не возвращает управление до тех пор, пока сервер не получит третий сегмент рукопожатия, то есть пока не пройдет половина периода RTT после завершения функции connect.
Мы намеренно запускаем и клиент, и сервер на одном узле — так проще всего экспериментировать с клиент-серверными приложениями. Поскольку клиент и сервер запущены на одном узле, функция
отображает теперь две дополнительные строки вывода, соответствующие соединению TCP:netstatl
inux % <b>netstat -a</b>Proto Recv-Q Send-Q Local Address Foreign Address Statetcp 0 0 localhost:9877 localhost:42758 ESTABLISHEDtcp 0 0 localhost:42758 localhost:42758 ESTABLISHEDtcp 0 0 *:9877 *:* LISTENПервая из строк состояния
соответствует дочернему сокету сервера, поскольку локальным портом является порт 9877. Вторая строкаESTABLISHED— это клиентский сокет, поскольку локальный порт — порт 42 758. Если мы запускаем клиент и сервер на разных узлах, на узле клиента будет отображаться только клиентский сокет, а на узле сервера — два серверных сокета.ESTABLISHEDДля проверки состояний процессов и отношений между ними можно также использовать команду
:pslinux % <b>ps -t pts/6 -o pid,ppid,tty,stat,args,wchan</b>PID PPID TT STAT COMMAND WCHAN22038 22036 pts/6 S -bash wait417870 22038 pts/6 S ./tcpserv01 wait_for_connect19315 17870 pts/6 S ./tcpserv01 tcp_data_wait19314 22038 pts/6 S ./tcpcli01 127.0.0.1 read_chanМы вызвали
с несколько необычным набором аргументов для того, чтобы получить всю необходимую для дальнейшего обсуждения информацию. Мы запустили клиент и сервер из одного окна (ps, что означает псевдотерминал 6). В колонкахpts/6иPIDпоказаны отношения между родительским и дочерним процессами. Можно точно сказать, что первая строкаPPIDсоответствует родительскому процессу, а вторая строкаtcpserv01— дочернему, поскольку PPID дочернего процесса — это PID родительского. Кроме того, PPID родительского процесса совпадает с PID интерпретатора команд (tcpserv01).bashКолонка
для всех трех сетевых процессов отмечена символомSTAT. Это означает, что процессы находятся в состоянии ожидания (sleeping). Если процесс находится в состоянии ожидания, колонкаSсообщит нам о том, чем он занят. В Linux значениеWCHANвыводится, если процесс блокируется функциейwait_for_connectилиaccept, значениеconnect— если процесс блокируется при вводе или выводе через сокет, atcp_data_wait— если процесс блокируется при терминальном вводе-выводе. Так что для наших трех сетевых процессов значенияread_chanвыглядят вполне осмысленно.WCHAN