Здесь мы игнорируем некоторые подробности. Прежде всего, алгоритм медленного запуска TCP ограничивает скорость, с которой сегменты начинают отправляться по соединению, которое до этого было неактивным. Далее, TCP часто подтверждает каждый второй сегмент, а не каждый сегмент, как мы это показываем. Все эти подробности описаны в главах 20 и 24 [111].
Нам необходимо понять принцип функционирования двустороннего канала и узнать, что такое его вместимость и как она влияет на размеры буферов сокетов на обоих концах соединения. Вместимость канала характеризуется произведением пропускной способности на задержку(bandwidth-delay product). Мы будем вычислять ее, умножая пропускную способность канала (в битах в секунду) на период обращения (RTT, round-trip time) (в секундах) и преобразуя результат из битов в байты. RTT легко измеряется с помощью утилиты
ping
.
Пропускная способность — это значение, соответствующее наиболее медленной связи между двумя конечными точками; предполагается, что это значение каким-то образом определено. Например, линия T1 (1 536 000 бит/с) с RTT 60 мс дает произведение пропускной способности на задержку, равное 11 520 байт. Если размеры буфера сокета меньше указанного, канал не будет заполнен и производительность окажется ниже предполагаемой. Большие буферы сокетов требуются, когда повышается пропускная способность (например, для линии T3, где она равна 45 Мбит/с) или когда увеличивается RTT (например, спутниковые каналы связи с RTT около 500 мс). Когда произведение пропускной способности на задержку превосходит максимальный нормальный размер окна TCP (65 535 байт), обоим концам соединения требуются также параметры TCP для канала с повышенной пропускной способностью (long fat pipe), о которых мы упоминали в разделе 2.6.
ПРИМЕЧАНИЕ
В большинстве реализаций размеры буферов отправки и приема ограничиваются некоторым предельным значением. В более ранних реализациях, происходящих от Беркли, верхний предел был около 52 000 байт, но в новых реализациях предел по умолчанию равен 256 000 байт или больше, и обычно администратор имеет возможность увеличивать его. К сожалению, не существует простого способа, с помощью которого приложение могло бы узнать этот предел. POSIX определяет функцию fpathconf, поддерживаемую большинством реализаций, а в качестве второго аргумента этой функции должна использоваться константа _PC_SOCK_MAXBUF. Приложение может также попытаться установить желаемый размер буфера сокета, а если попытка окажется неудачной, сократить размер вдвое и вызвать функцию снова. Наконец, приложение должно убедиться, что оно не уменьшает размер буфера по умолчанию, задавая свое собственное значение. В первую очередь следует вызвать getsockopt для определения значения, установленного по умолчанию, которое вполне может оказаться достаточным.
Параметры сокета SO_RCVLOWAT и SO_SNDLOWAT
Каждый сокет характеризуется также минимальным количеством данных (low- water mark) для буферов приема и отправки. Эти значения используются функцией
select
, как мы показали в разделе 6.3. Указанные параметры сокета позволяют нам изменять эти два значения.
Минимальное количество данных — это количество данных, которые должны находиться в приемном буфере сокета, чтобы функция
select
возвратила ответ «Сокет готов для чтения». По умолчанию это значение равно 1 для сокетов TCP и UDP. Минимальный объем для буфера отправки — это количество свободного пространства, которое должно быть в буфере отправки сокета, чтобы функция select возвратила «Сокет готов для записи». Для сокетов TCP по умолчанию оно обычно равно 2048. С UDP это значение используется так, как мы показали в разделе 6.3, но поскольку число байтов доступного пространства в буфере отправки для сокета UDP никогда не изменяется (поскольку UDP не хранит копии дейтаграмм, отправленных приложением), сокет UDP всегда готов для записи, пока размер буфера отправки сокета UDP больше минимального объема. Вспомните рис. 2.16: UDP не имеет настоящего буфера отправки, у него есть только параметр размера буфера отправки.
Параметры сокета SO_RCVTIMEO и SO_SNDTIMEO
Эти два параметра сокета позволяют нам устанавливать тайм-аут при получении и отправке через сокет. Обратите внимание, что аргумент двух функций
sockopt
— это указатель на структуру
timeval
, ту же, которую использует функция
select
(раздел 6.3). Это позволяет использовать для задания тайм-аута секунды и миллисекунды. Отключение тайм-аута осуществляется установкой его значения в 0 секунд и 0 миллисекунд. Оба тайм-аута по умолчанию отключены.
Тайм-аут приема влияет на пять функций ввода:
read
,
readv
,
recv
,
recvfrom
и
recvmsg
. Тайм-аут отправки влияет на пять функций вывода:
write
,
writev
,
send
,
sendto
и
sendmsg
. Более подробно о тайм-аутах сокета мы поговорим в разделе 14.2.
ПРИМЕЧАНИЕ
Эти два параметра сокета и концепция тайм-аута сокетов вообще были добавлены в реализации 4.3BSD Reno.
В реализациях, происходящих от Беркли, указанные параметры инициализируют таймер
отсутствия активности, а не абсолютный таймер системного вызова чтения или записи. На с. 496 и 516 [128] об этом рассказывается более подробно.
Параметры сокета SO_REUSEADDR и SO_REUSEPORT
Параметр сокета
SO_REUSEADDR
служит для четырех целей.
1. Параметр
SO_REUSEADDR
позволяет прослушивающему серверу запуститься и с помощью функции
bind
связаться со своим заранее известным портом, даже если существуют ранее установленные соединения, использующие этот порт в качестве своего локального порта. Эта ситуация обычно возникает следующим образом:
1) запускается прослушивающий сервер;
2) от клиента приходит запрос на соединение, и для обработки этого клиента генерируется дочерний процесс;
3) прослушивающий сервер завершает работу, но дочерний процесс продолжает обслуживание клиента на существующем соединении;
4) прослушивающий сервер перезапускается.
По умолчанию, когда прослушивающий сервер перезапускается при помощи вызова функций
socket
,
bind
и
listen
, вызов функции
bind
оказывается неудачным, потому что прослушивающий сервер пытается связаться с портом, который является частью существующего соединения (обрабатываемого ранее созданным дочерним процессом). Но если сервер устанавливает параметр сокета
SO_REUSEADDR
между вызовами функций
socket
и
bind
, последняя выполнится успешно. Все серверы TCP должны задавать этот параметр сокета, чтобы позволить перезапускать сервер в подобной ситуации.
ПРИМЕЧАНИЕ
Этот сценарий вызывает больше всего вопросов в Usenet.
2. Параметр
SO_REUSEADDR
позволяет множеству экземпляров одного и того же сервера запускаться на одном и том же порте, если все экземпляры связываются с различными локальными IP-адресами. Это типичная ситуация для узла, на котором размещаются несколько серверов HTTP, использующих технологию альтернативных IP-адресов, или псевдонимов (IP alias technique) (см. раздел А.4). Допустим, первичный IP-адрес локального узла — 198.69.10.2, но он имеет два альтернативных адреса — 198.69.10.128 и 198.69.10.129. Запускаются три сервера HTTP. Первый сервер с помощью функции bind свяжется с локальным IP-адресом 198.69.10.128 и локальным портом 80 (заранее известный порт HTTP). Второй сервер с помощью функции
bind
свяжется с локальным IP-адресом 198.69.10.129 и локальным портом 80. Но второй вызов функции
bind
не будет успешным, пока не будет установлен параметр
SO_REUSEADDR
перед обращением к ней. Третий сервер вызовет функцию bind с универсальным адресом в качестве локального IP-адреса и локальным портом 80. И снова требуется параметр
SO_REUSEADDR
, для того чтобы последний вызов оказался успешным. Если считать, что установлен параметр
SO_REUSEADDR
и запущены три сервера, то входящие запросы TCP на соединение с IP-адресом получателя 198.69.10.128 и портом получателя 80 доставляются на второй сервер, входящие запросы на соединение с IP-адресом получателя 198.69.10.129 и портом получателя 80 — на третий сервер, а все остальные входящие запросы TCP на соединение с портом получателя 80 доставляются на первый сервер. Этот сервер обрабатывает запросы, адресованные на 198.69.10.2, в дополнение к другим альтернативным IP-адресам, для которых этот узел может быть сконфигурирован. Символ подстановки означает в данном случае «все, для чего не нашлось более точного совпадения». Заметим, что этот сценарий, допускающий множество серверов для данной службы, обрабатывается автоматически, если сервер всегда устанавливает параметр сокета
SO_REUSEADDR
(как мы рекомендуем).
TCP не дает нам возможности запустить множество серверов, которые с помощью функции
bind
связываются с одним и тем же IP-адресом и одним и тем же портом: это случай полностью дублированного связывания( completely duplicate binding). То есть мы не можем запустить один сервер, связывающийся с адресом 198.69.10.2 и портом 80, и другой сервер, также связывающийся с адресом 198.69.10.2 и портом 80, даже если для второго сервера мы установим параметр
SO_REUSEADDR
.
По соображениям безопасности некоторые операционные системы запрещают связывать несколько серверов с адресом подстановки, то есть описанный выше сценарий не работает даже с использованием параметра
SO_REUSEADDR
. В такой системе сервер, связываемый с адресом подстановки, должен запускаться последним. Таким образом предотвращается привязка сервера злоумышленника к IP-адресу и порту, которые уже обрабатываются системной службой. Особенно это важно для службы NFS, которая обычно не использует выделенный порт.