Чтобы увидеть, в чем заключается проблема с нашей функцией
str_cli
, представленной в листинге 6.1, будем считать, что файл ввода содержит только девять строк. Последняя строка отправляется в момент времени 8, как показано на рис. 6.9. Но мы не можем закрыть соединение после записи этого запроса, поскольку в канале еще есть другие запросы и ответы. Причина возникновения проблемы кроется в нашем способе обработки конца файла при вводе, когда процесс возвращается в функцию
main
, которая затем завершается. Но в пакетном режиме конец файла при вводе не означает, что мы закончили читать из сокета — в нем могут оставаться запросы к серверу или ответы от сервера.
Нам нужен способ закрыть одну половину соединения TCP. Другими словами, мы хотим отправить серверу сегмент FIN, тем самым сообщая ему, что закончили отправку данных, но оставляем дескриптор сокета открытым для чтения. Это делается с помощью функции
shutdown
, которая описывается в следующем разделе.
Вообще говоря, буферизация ввода-вывода для повышения производительности приводит к усложнению сетевых приложений (от чего пострадала и программа в листинге 6.1). Рассмотрим пример, в котором из стандартного потока ввода считывается несколько строк текста. Функция
select
передаст управление строке 20, в которой функция
fgets
считает доступные данные в буфер библиотеки
stdio
. Однако эта функция возвратит приложению только одну строку, а все остальные так и останутся в буфере. Считанная строка будет отправлена серверу, после чего будет снова вызвана функция
select
, которая будет ждать появления новых данных в стандартном потоке ввода несмотря на наличие еще не обработанных строк в буфере
stdio
. Причина в том, что
select
ничего не знает о буферах
stdio
и сообщает о доступности дескриптора для чтения с точки зрения системного вызова
read
, а не библиотечного вызова
fgets
. По этой причине использование
fgets
и
select
в одной программе считается опасным и требует особой осторожности.
Та же проблема связана с вызовом
readline
в листинге 6.1. Теперь данные скрываются от функции
select
уже не в буфере
stdio
, а в буфере
readline
. Вспомните, что в разделе 3.9 мы создали функцию, проверявшую состояние буфера
readline
. Мы могли бы воспользоваться ею перед вызовом
select
, чтобы проверить, нет ли в буфере
readline
данных, дожидающихся обработки. Наша программа усложнится еще больше, если мы допустим, что буфер
readline
может содержать лишь часть строки (то есть нам придется дожидаться считывания этой строки целиком).
Проблемы буферизации мы постараемся решить в усовершенствованной версии
str_cli
в разделе 6.7.
6.6. Функция shutdown
Обычный способ завершить сетевое соединение — вызвать функцию
close
. Но у функции
close
есть два ограничения, которых лишена функция
shutdown
:
1. Функция close последовательно уменьшает счетчик ссылок дескриптора и закрывает сокет, только если счетчик доходит до нуля. Мы рассматривали это в разделе 4.8. Используя функцию
shutdown
, мы можем инициировать обычную последовательность завершения соединения TCP (четыре сегмента, начинающихся с FIN, на рис. 2.5) независимо от значения счетчика ссылок.
2. Функция
close
завершает оба направления передачи данных — и чтение, и запись. Поскольку соединение TCP является двусторонним, возможны ситуации, когда нам понадобится сообщить другому концу соединения, что мы закончили отправку, даже если на том конце соединения имеются данные для отправки нам. Это случай, рассмотренный в предыдущем разделе при описании работы нашей функции
str_cli
в пакетном режиме. На рис. 6.10 показаны типичные вызовы функций в этом сценарии.
Рис. 6.10. Вызов функции shutdown для закрытия половины соединения TCP
#include
int shutdown(int
Действие функции зависит от значения аргумента
howto
.
SHUT_RD
. Закрывается считывающая половина соединения: из сокета больше нельзя считывать данные, и все данные, находящиеся в данный момент в буфере приема сокета, сбрасываются. Процесс больше не может выполнять функции чтения из сокета. Любые данные для сокета TCP, полученные после вызова функции
shutdown
с этим аргументом, подтверждаются и «молча» игнорируются.