Rambler's Top100 Service калинин.ru / программирование / сети /  << 29.10.00 >>

lingering close

Когда программа выкачивает один файл с удаленного сервера с использованием протокола TCP, а после этого сразу же "отваливается", то проблем, скорее всего, не возникнет никаких. Допустим, для определенности, что эта программа использует HTTP для передачи данных. В этом случае вся программа сводится к тому, что открывается сокет, указывается адрес удаленной машины и порт, туда передается, например, "GET /", после чего программа выкачивает все, что ей в этот сокет кинули. Это очень просто и каждый программист, даже никогда до этих пор не работавший с протоколами семейства TCP/IP, прочитав содержимое man-страниц, сможет написать подобную программу.

Тем не менее, редко когда программа ограничивается выкачиванием одного лишь файла. Через некоторое время становится нужно выкачать с удаленной машины еще что-нибудь, потом обработать редиректы, потом еще кое-что появится... вы и оглянуться не успеете, как будете анализировать содержимое файла robots.txt. Тем не менее, программа, которая выкачивает один файл, и программа, которая выкачивает несколько файлов, совсем не одно и то же.

Причины тому достаточно прозаические. Все дело в том, что программа, которая выкачивает один файл, потом другой и только потом третий, будет работать очень медленно. И это несмотря на скорость вашего канала: программа, которая работает по такой схеме, большую часть времени будет проводить в ожидании соединения или прихода данных.

Поэтому ни веб-клиенты, ни веб-сервера обычно не строятся по схеме изложенной выше, а являются либо многопоточными программами, либо используют асинхронные или неблокирующие функции ввода-вывода, либо выбирают "готовые" дескрипторы из некоторого списка... в общем, вариантов много и я не буду сейчас их все расписывать. Достаточно понять то, что программа будет несколько сложнее чем ее первоначальный вариант и будет управляться событиями ввода-вывода от разных соединений, а не ожидать окончания одного.

И все хорошо до тех пор, пока количество файлов, подлежащих одновременному выкачиванию не становится очень большим, а пропускная способность вашего канала уже достаточно велика... как это произойдет, то через некоторое время, скорее всего, у вас вдруг перестанут открываться сокеты с ошибкой наподобие ENOBUF.

Происходит это потому, что в операционной системе имеется естественное ограничение на количество одновременно открытых сокетов. Связано это с тем, что на каждый сокет выделяется буфер, используемый для операций чтения и записи, который занимает некоторое место. Обычно этот буфер выделяется из некоторого пула и, хотя я не знаю как обстоит с ним дело в, например, Windows, но могу с некоторой долей уверенности сказать, что и там он не резиновый. В BSD, к примеру, его размер можно указать в конфигурации ядра. Тем не менее, количество открытых сокетов в вашей программе не будет велико, так в чем же дело?

Ответ достаточно прост. То что вы вызвали функцию ядра close() на сокет, еще не означает, что сразу же будут освобождены все структуры, с ним связанные. Это, всего-лищь означает, что этот сокет больше не будет доступен вашей программе, а ядро еще некоторое время будет удерживать его в состоянии вроде TIME_WAIT. Время между вызовом close() и реальным освобождением сокета расчитывается исходя из максимального времени существования пакета в сети.

Существуют два решения этой проблемы. Первое заключается в увеличении количества памяти, выделяемой операционной системой под сокеты. Второе --- в использовании того, что в англоязычной литературе называется "lingering close".

Существует атрибут у сокета, называемый SO_LINGER. При его помощи можно изменить поведение close() на такое, при котором вызывающий процесс будет переведен в состояние ожидания реального закрытия сокета. Выставив этот атрибут, вам потребуется также указать время ожидания для данного сокета. Второе решение рассматриваемой проблемы заключается в том, что бы "включить" SO_LINGER для сокетов и установить время ожидания в 0. В этом случае все структуры, связанные с сокетами, будут освобождены сразу. Делается это следующим образом:

struct linger l = { 1, 0 };
setsockopt(sock, SOL_SOCKET, SO_LINGER, &l, sizeof(struct linger));
close(sock);

Тем не менее, "второе решение" чревато новыми проблемами: через некоторое время вы обнаружите странные ситуации обрыва связи с удаленной машиной при одновременном выкачивании с нее нескольких файлов (в случае одного файла все будет в порядке).

Тут надо четко понимать, откуда взялось время между вызовом close() и реальным освобождением сокета. Все дело в том, что пакеты в сети не идут напрямую от одного адреса к другому, а "блуждают" по сети в поисках своего адресата. При использовании TCP, пакеты характеризуются четырьмя параметрами: IP-адресом отправителя, портом отправителя, IP-адресом получателя и портом получателя. Поэтому, если закрыть сокет сразу, а потом случайно открыть соединение с той же удаленной машиной по тому же локальному порту, то пакеты от старого соединения, которые все еще "блуждают", будут восприняты как реальные данные нового соединения! В частности, очень просто получить потверждающий FIN, который пришел в качестве реакции на предыдущий close(), в результате которого произойдет разрыв соединения.

И еще раз. При повторном HTTP запросе на сервер у вас три из четырех параметров в TCP пакете будут точно такими же, как и при первом HTTP запросе, то есть IP-адреса компьютеров и порт сервера (80). Вероятность же того, что на локальной машине вы получите тот же порт, что и в предыдущий раз, не нулевая и вполне реальная. Таким образом, данные могут быть повреждены.

Существует еще одно объяснение появления времени между вызовом close() и освобождением структур, связанных с сокетами, которое, правда, не имеет прямого отношения к рассматриваемой гипотетической программе. Все дело в том, что при записи в сокет, вызов close() может последовать до того, как данные будут реально переданы на удаленную машину. В этом случае, реализация TCP должна будет "подождать" потверждение о приеме данных удаленной машиной.

Таким образом, возвращаясь к проблеме с сокетами в состоянии TIME_WAIT. Самым правильным будет увеличить размер памяти под сокеты: в этом случае работа вашей программы сразу же станет более стабильной. Lingering close можно применять только в том случае, когда вы уверены в том, что больше соединений с удаленной машиной по данному IP-адресу или порту не будет. Тогда это допустимо. Иначе --- надо подождать.

Время же ожидания, как я уже сказал выше, расчитывается исходя из максимального времени жизни пакета. Реально это время составляет несколько минут и зависит от конкретной реализации TCP.

Резюме

Lingering close с нулевым временем ожидания можно применять только в тех случаях, когда доподлинно известно, что "блуждающие" пакеты ничем больше повредить не могут.


Версия для печати


  Ссылки по теме:
Stevens
   Network programming, volume 1.
А. Робачевский.
   Операционная система Unix.
  Рядом в разделе:
Неблокирующий connect() (01.12.00)
   В продолжение темы о замене блокирующего вызова , хочется рассказать о другой функции интерфейса сокетов, . Она имеет следующий прототип: int...   >>>>
Определение ip-адреса по имени хоста, adns (05.11.00)
   Есть такой, характерный для организации "традиционного" UNIX'а, системный вызов под названием : struct hostent * gethostbyname(const char *name); Традиционен он тем,...   >>>>
  Рядом по дате:
Операционная система Unix (31.10.00)
   Unix получил очень широкое распространение в современном компьютерном мире. При этом, даже если большая часть домашних компьютеров работает под управлением операционной...   >>>>
Глупости при создании сайтов (26.10.00)
   Нет, я не собираюсь в очередной раз рассказывать о том, как писать правильный HTML, или объяснять куда нужно "пихать" баннеры. Для...   >>>>
  Содержание:
Заглавная страница
Мой блог
Мое резюме
Дайджест
Программирование
   C&C++
Сети
Unix
Алгоритмы
Оптимизация
Соревнования
Отвлеченно
XML
TeX
Просто так
Студенческое
Туризм
  Байки
Фотографии
Комментарии
   Книги
Web-ресурсы
Фильмы
Интернет
Программное обеспечение
Жизнь
Благодарности
Форум
Хронология
 
  В этом разделе:
События ядра в FreeBSD. (16.07.01)
   Обработка большого количества сетевых соединений всегда затруднительна. Мало того, не существует стандартных решений, подходящих для проблем любого вида, в которых возникает...   >>>>
Обзор CORBA (14.01.01)
   CORBA (расшифровывается как Common Object Request Broker) это технология, которая позволяет рассматривать компоненты распределенной системы как объекты, отвечающие некоторым определенным интерфейсам....   >>>>
"Тонкий" клиент (19.12.00)
   В предыдущей заметке Gregory Liokumovich рассказывал о применении событийной модели WinSock для программирования сетевых приложений. На самом деле, как мне кажется,...   >>>>
Событийная модель в WinSock (12.12.00)
   Автором этого текста является Gregory Liokumovich ( ). Он любезно прислал мне этот текст, связанный с программированием сетей при помощи интерфейса...   >>>>
Неблокирующий connect() (01.12.00)
   В продолжение темы о замене блокирующего вызова , хочется рассказать о другой функции интерфейса сокетов, . Она имеет следующий прототип: int...   >>>>
Определение ip-адреса по имени хоста, adns (05.11.00)
   Есть такой, характерный для организации "традиционного" UNIX'а, системный вызов под названием : struct hostent * gethostbyname(const char *name); Традиционен он тем,...   >>>>
lingering close (29.10.00)
   Когда программа выкачивает один файл с удаленного сервера с использованием протокола TCP, а после этого сразу же "отваливается", то проблем, скорее...   >>>>
Содержание раздела полностью...
   Примерно в тоже время
Операционная система Unix (31.10.00)
   Unix получил очень широкое распространение в современном компьютерном мире. При этом, даже если большая часть домашних компьютеров работает под управлением операционной...   >>>>
Глупости при создании сайтов (26.10.00)
   Нет, я не собираюсь в очередной раз рассказывать о том, как писать правильный HTML, или объяснять куда нужно "пихать" баннеры. Для...   >>>>
Хронология полностью...
   Содержание
Заглавная страница
Мой блог
Мое резюме
Дайджест
Программирование
  C&C++
Сети
Unix
Алгоритмы
Оптимизация
Соревнования
Отвлеченно
XML
TeX
Туризм
  Байки
Фотографии
Комментарии
  Книги
Web-ресурсы
Фильмы
Интернет
Программное обеспечение
Жизнь
Студенческое
Просто так
Благодарности
Форум
Хронология
© 2000-2008, Andrey L. Kalinin
mailto:andrey@kalinin.ru
Rambler's Top100