UNIX: разработка сетевых приложений
5.2. Наши клиент-серверные программы не работают с двоичными файлами. Допустим, что первые 3 байта в файле являются двоичной единицей (1), двоичным нулем (0) и символом новой строки. При вызове функции
в листинге 5.4 либо считываетсяfgets- 1 символов, либо считываются символы до символа новой строки или до конца файла. В данном примере функция считает три символа, а затем прервет строку нулевым байтом. Но вызов функцииMAXLINEв листинге 5.4 возвращает значение 1, так как она остановится на первом нулевом байте. Один байт посылается серверу, но сервер блокируется в своем вызове функцииstrlen, ожидая символа новой строки. Клиент блокируется, ожидая ответа от сервера. Такое состояние называется зависанием, или взаимной блокировкой: оба процесса блокированы и при этом каждый ждет от другого некоторого действия, которое никогда не произойдет. Проблема заключается в том, что функцияreadlineобозначает нулевым байтом конец возвращаемых ею данных, поэтому данные, которые она считывает, не должны содержать нулевой байт.fgets5.3. Программа
преобразует входные строки в NVT ASCII (см. раздел 26.4 книги [111]), что означает прерывание каждой строки 2-символьной последовательностью CR (carriage return — возврат каретки) и LF (linefeed — новая строка). Наш клиент добавляет только разделитель строк (newline), который в действительности является символом новой строки (linefeed, LF). Тем не менее можно использовать клиент Telnet для связи с нашим сервером, поскольку наш сервер отражает каждый символ, включая CR, предшествующий каждому разделителю строк.Telnet5.4. Нет, последние два сегмента из последовательности завершения соединения не посылаются. Когда клиент посылает серверу данные после уничтожения дочернего процесса сервера (ввод строки
, см. раздел 5.12), сервер TCP отвечает сегментом RST. Сегмент RST прекращает соединение, а также предотвращает переход в состояние TIME_WAIT на стороне сервера (конец соединения, осуществивший активное закрытие).another line5.5. Ничего не меняется, потому что процесс, запущенный на узле сервера, создает прослушиваемый сокет и ждет прибытия запросов на соединение. На третьем шаге мы посылаем сегмент данных, предназначенный для установленного соединения TCP (состояние ESTABLISHED). Наш сервер с прослушиваемым сокетом не увидит этот сегмент данных, и TCP сервера по-прежнему будет посылать клиенту сегмент RST.
5.6. В листинге Д.1 [1] приведена программа. Запуск этой программы в Soalris генерирует следующий вывод:
solaris % <b>tsigpipe 192.168.1.10</b>SIGPIPE receivedwrite error: Broken pipeНачальный вызов функции
и переход в режим ожидания на 2 с нужен, чтобы сервер времени и даты отправил ответ и закрыл свой конец соединения. Первая функцияsleepотправляет сегмент данных серверу, который отвечает сегментом RST (поскольку сервер времени и даты полностью закрыл свой сокет). Обратите внимание, что наш TCP позволяет писать в сокет, получивший сегмент FIN. Второй вызов функцииwriteпозволяет получить от сервера сегмент RST, а во втором вызове функцииsleepгенерируется сигналwrite. Поскольку наш обработчик сигналов возвращает управление, функция write возвращает ошибкуSIGPIPE.EPIPEЛистинг Д.1. Генерация SIGPIPE
//tcpcliserv/tsigpipe.c1 #include "unp.h"2 void3 sig_pipe(int signo)4 {5 printf("SIGPIPE received\n");6 return;7 }8 int9 main(int argc, char **argv)10 {11 int sockfd;12 struct sockaddr_in servaddr;13 if (argc != 2)14 err_quit("usage: tcpcli <Ipaddress>");15 sockfd = Socket(AF_INET, SOCK_STREAM, 0);16 bzero(&servaddr, sizeof(servaddr));17 servaddr.sin_family = AF_INET;18 servaddr.sin_port = htons(13); /* сервер времени и даты */19 Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);20 Signal(SIGPIPE, sig_pipe);21 Connect(sockfd, (SA*)&servaddr, sizeof(servaddr));22 sleep(2);23 Write(sockfd, "hello", 5);24 sleep(2);25 Write(sockfd, "world", 5);26 exit(0);27 }5.7. В предположении, что узел сервера поддерживает модель системы с гибкой привязкой (см. раздел 8.8), все будет работать. Узел сервера примет IP-дейтаграмму (которая в данном случае содержит TCP-сегмент), прибывшую на самый левый канал, даже если IP-адрес получателя является адресом самого правого канала. Это можно проверить, если запустить наш сервер на узле
(см. рис. 1.7), а затем запустить клиент на узлеlinux, но на стороне клиента задать другой IP-адрес сервера (206.168.112.96). После установления соединения, запустив на стороне сервера программуsolaris, мы увидим, что локальный IP-адрес является IP-адресом получателя из клиентского сегмента SYN, а не IP-адресом канала, на который прибыл сегмент SYN (как отмечалось в разделе 4.4).netstat5.8. Наш клиент был запущен в системе Intel с прямым порядком байтов, где 32-разрядное целое со значением 1 хранится так, как показано на рис. Д.1.
Рис. Д.1. Представление 32-разрядного целого числа 1 в формате прямого порядка байтов
Четыре байта посылаются на сокет в следующем порядке: A, A + 1, A + 2 и A + 3, и там хранятся в формате обратного порядка байтов, как показано на рис. Д.2.
Рис. Д.2. Представление 32-разрядного целого числа с рис. Д.1 в формате обратного порядка байтов
