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, или объяснять куда нужно "пихать" баннеры. Для... >>>>
|
|
| | Содержание: |
 |
|
|
| | В этом разделе: |
 |
| События ядра в 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-ресурсы
Фильмы
Интернет
Программное обеспечение
Жизнь
|
| Студенческое |
| Просто так |
| Благодарности |
| Форум |
| Хронология |
|