буферов переменного размера. (в) Один большой циклический буфер для одного соединения
Другой способ решения проблемы состоит в использовании буферов переменного размера (илл. 6.15 (б)). Преимущество этого метода заключается в оптимальном использовании памяти, но платой за это является более сложное управление буферами. Третий вариант состоит в выделении соединению единого большого циклического буфера (илл. 6.15 (в)). Эта простая и изящная схема работает независимо от размера сегментов, однако она эффективно использует память, только если все соединения сильно нагружены.
При открытии и закрытии соединений и при изменении формы трафика отправитель и получатель должны динамически изменять выделенные буферы. Следовательно, транспортный протокол должен позволять отправителю отправлять запросы на выделение буфера на другой стороне. Буферы могут выделяться для каждого соединения или коллективно на все соединения между двумя хостами. В качестве альтернативы запросам получатель, зная состояние своих буферов, но не зная, какой трафик ему будет предложен, может сообщить отправителю, что он зарезервировал для него Х буферов. При увеличении числа открытых соединений может потребоваться уменьшить количество или размеры выделенных буферов. Протокол должен предоставлять такую возможность.
В отличие от протокола раздвижного окна, описанного в главе 3, для реализации динамического выделения буферов следует отделить буферизацию от подтверждений. Динамическое выделение буферов на деле означает использование окна переменного размера. Сначала отправитель запрашивает определенное число буферов согласно своим потребностям. Получатель выделяет столько, сколько может. При отправлении каждого сегмента отправитель должен уменьшать число буферов на единицу, а когда оно достигнет нуля, он должен остановиться. Получатель отправляет обратно на попутных сегментах отдельно подтверждения и сведения об имеющихся у него свободных буферах. Эта схема используется в TCP; при этом информация о буферах хранится в поле заголовка Window size (Размер окна).
На илл. 6.16 показан пример управления динамическим окном в дейтаграммной сети с 4-битными порядковыми номерами. Данные передаются в виде сегментов от хоста A к хосту B, а подтверждения и запросы на предоставление буферов идут в обратном направлении (также в виде сегментов). Вначале A запрашивает 8 буферов, но ему выделяется только 4. Затем он отправляет 3 сегмента; последний теряется. На шаге 6 A получает подтверждение получения переданных им сегментов 0 и 1, в результате чего он может освободить буферы и отправить еще 3 сегмента (2, 3 и 4). Хост A знает, что сегмент номер 2 он уже отправлял, поэтому думает, что может передать сегменты 3 и 4 (что он и делает). На этом шаге он блокируется, так как его счетчик буферов достиг нуля и ждет предоставления новых буферов. На шаге 9 наступает тайм-аут хоста А, так как он до сих пор не получил подтверждения для сегмента 2. Этот сегмент отсылается еще раз. В строке 10 хост B подтверждает получение всех сегментов, включая 4-й, но отказывается предоставлять буферы хосту A. Такая ситуация невозможна в протоколах с фиксированным размером окна, описанных в главе 3. Следующий сегмент, отправленный хостом B, разрешает хосту A передать еще один сегмент. Это происходит, когда у B появляется свободное буферное пространство, — скорее всего, потому, что пользователь службы принял больше данных.
Проблемы при такой схеме выделения буферов в дейтаграммных сетях могут возникнуть, если потеряется управляющий сегмент (а это действительно может произойти). Взгляните на строку 16. Хост B выделил хосту A дополнительные буферы, но сообщение об этом потерялось. Вот так неожиданность! Поскольку получение управляющих сегментов не подтверждается и они не отправляются повторно по тайм-ауту, хост A блокируется всерьез и надолго. Для предотвращения такой тупиковой ситуации каждый хост должен периодически отправлять управляющий сегмент с подтверждением и сведениями о состоянии буферов для каждого соединения. Это позволит в конце концов выбраться из тупика.
До сих пор мы предполагали, что единственное ограничение, накладываемое на скорость передачи данных, состоит в объеме свободного буферного пространства у получателя. Однако часто это не так. По мере колоссального снижения цен (некогда очень высоких) на микросхемы памяти и жесткие диски становится возможным оборудовать хосты таким количеством памяти, что проблема нехватки буферов возникает очень редко, даже если соединение охватывает крупные территории. Конечно, выбранный буфер должен быть достаточно большим; в случае TCP это требование не всегда выполнялось (Чжан и др.; Zhang et al., 2002).
Илл. 6.16. Динамическое выделение буферов. Стрелками показано направление передачи. Многоточие (…) означает потерянный сегмент
Если размер буферов перестанет ограничивать максимальный поток, возникнет другое узкое место: пропускная способность сети. Допустим, максимальная скорость обмена фреймами между соседними маршрутизаторами составляет x фреймов в секунду, а между двумя хостами имеется k непересекающихся путей. Сколько бы ни было буферов у обоих хостов, они не смогут пересылать друг другу больше чем kx сегментов в секунду. И если отправитель будет передавать быстрее, то сеть будет перегружена.
Требуется механизм, ограничивающий передачу данных со стороны отправителя, который основан не столько на емкости буферов получателя, сколько на пропускной способности сети. В 1975 году Белснес (Belsnes) предложил использовать для управления потоком данных схему раздвижного окна, в которой отправитель динамически приводит размер окна в соответствие с пропускной способностью сети.
Таким образом, раздвижное окно позволяет одновременно реализовать и управление потоком, и контроль перегрузки. Если сеть может обработать c сегментов в секунду, а время цикла (включая передачу, распространение, ожидание в очередях, обработку получателем и возврат подтверждения) равно r, тогда размер окна отправителя должен быть равен cr. При таком окне отправитель максимально использует канал. Любое снижение производительности сети приведет к его блокировке. Так как пропускная способность меняется с течением времени, размер окна должен настраиваться довольно часто, чтобы отслеживать изменения пропускной способности. Как будет показано ниже, в TCP используется похожая схема.
6.2.5. Мультиплексирование
Мультиплексирование (multiplexing), или объединение нескольких сеансов связи в одном соединении, виртуальном канале и одной физической линии, играет важную роль на нескольких уровнях сетевой архитектуры. На транспортном уровне такая потребность возникает в нескольких случаях. Например, если у хоста имеется только один сетевой адрес, он используется всеми соединениями транспортного уровня. Необходим способ, с помощью которого можно различать, какому процессу нужно передать входящий сегмент. Пример мультиплексирования показан на илл. 6.17 (а): четыре различных соединения транспортного уровня используют одно сетевое соединение (например, один IP-адрес) с удаленным хостом.
Илл. 6.17. Мультиплексирование: (а) прямое; (б) обратное
Мультиплексирование может применяться на транспортном уровне и по другой причине. Предположим, что хост может использовать несколько различных сетевых путей. Если пользователю требуется больше пропускной способности или более высокая надежность, чем может предоставить один сетевой путь, можно создать соединение, распределяющее трафик между путями, используя их поочередно (илл. 6.17 (б)). Такой метод называется обратным мультиплексированием (inverse multiplexing). При открытии k сетевых соединений эффективная пропускная способность может увеличиться в k раз. Примером обратного мультиплексирования является протокол SCTP, позволяющий устанавливать соединение с множественными сетевыми интерфейсами. TCP, наоборот, использует отдельный сокет. Обратное мультиплексирование применяется и на канальном уровне; при этом несколько медленных каналов связи объединяются в один, работающий гораздо быстрее.
6.2.6. Восстановление после сбоев
Если хосты и маршрутизаторы подвержены сбоям или соединения длятся достаточно долго (например, при загрузке мультимедиа или программного обеспечения), вопрос восстановления после сбоев особенно актуален. Если транспортная подсистема полностью размещается в хостах, восстановление после отказов сети и маршрутизаторов не вызывает затруднений. Транспортные подсистемы постоянно ожидают потери сегментов и знают, как с этим бороться: для этого выполняются повторные передачи.
Более серьезную проблему представляет восстановление после сбоя хоста. В частности, клиентам может потребоваться возможность продолжать работу после отказа и быстрой перезагрузки сервера. Чтобы пояснить, в чем тут сложность, предположим, что один хост (клиент) отправляет длинный файл другому хосту (файловому серверу) с помощью простого протокола с остановкой и ожиданием подтверждения. Транспортный уровень сервера просто передает приходящие сегменты один за другим пользователю транспортного уровня. Получив половину файла, сервер сбрасывается и перезагружается, после чего все его таблицы заново инициализируются, поэтому он не знает, на чем он остановился.
Пытаясь восстановить предыдущее состояние, сервер может разослать широковещательный сегмент всем хостам, объявляя, что он только что перезагрузился, и обращаясь с просьбой к своим клиентам сообщить ему о состоянии всех открытых соединений. Каждый клиент находится в одном из двух состояний: один неподтвержденный сегмент (S1) или ни одного неподтвержденного сегмента (S0). Этой информации клиенту должно быть достаточно, чтобы решить, передавать ему повторно последний сегмент или нет.
На первый взгляд все очевидно: узнав о перезапуске сервера, клиент должен передать повторно последний неподтвержденный сегмент. То есть повторная передача требуется, только если клиент находится в состоянии S1. Однако при более детальном рассмотрении выясняется, что все не так просто. Например, рассмотрим ситуацию, в которой транспортная подсистема сервера сначала отправляет подтверждение и лишь затем передает сегмент прикладному процессу. Запись сегмента в выходной поток и отправка подтверждения — это два отдельных события, которые нельзя выполнить одновременно. Если сбой произойдет после отправки подтверждения, но до того, как выполнена запись, клиент получит подтверждение, а при перезапуске сервера окажется в состоянии S0. В результате он не отправит сегмент повторно, так как будет считать, что сегмент уже получен, что приведет к потере сегмента.