6.2. Элементы транспортных протоколов

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

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

Илл. 6.7. Окружение: (а) канального уровня; (б) транспортного уровня

Во-первых, при двухточечном соединении по проводу или оптоволоконной линии маршрутизатор обычно не должен указывать, с каким маршрутизатором он хочет обменяться данными, — каждая выходная линия ведет к конкретному маршрутизатору. На транспортном уровне требуется явно указывать адрес получателя.

Во-вторых, процесс установки соединения по проводу (см. илл. 6.7 (а)) прост: противоположная сторона всегда присутствует (если только она не вышла из строя). В любом случае работы не очень много. Даже в случае беспроводного соединения ситуация не слишком отличается. Простой отправки сообщения достаточно, чтобы оно дошло до всех адресатов. Если подтверждение о получении не приходит (вследствие ошибок), сообщение может быть отправлено повторно. На транспортном уровне начальная установка соединения, как будет показано ниже, происходит достаточно сложно.

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

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


6.2.1. Адресация

Когда один прикладной процесс желает установить соединение с другим прикладным процессом, он должен его конкретно указать. Обычно для этого задаются транспортные адреса, куда процессы отправляют запросы на установление соединения. В интернете такие конечные точки на транспортном уровне называются портами. Для их обозначения мы будем пользоваться нейтральным термином точка доступа к службам транспортного уровня (Transport Service Access Point, TSAP). Аналогичные конечные точки сетевого уровня (то есть адреса сетевого уровня) называются точками доступа к сетевым службам (Network Service Access Point, NSAP). Примерами NSAP являются IP-адреса.

На илл. 6.8 показаны взаимоотношения между NSAP, TSAP и транспортным соединением. Прикладные процессы клиента и сервера могут связываться с локальной TSAP для установления соединения с удаленной TSAP. Такие соединения проходят через NSAP на каждом хосте, как показано на рисунке. TSAP нужны для того, чтобы различать конечные точки, совместно использующие NSAP, в сетях, где у каждого компьютера есть своя NSAP.

Возможный сценарий для транспортного соединения выглядит следующим образом.


1. Процесс почтового сервера подсоединяется к точке доступа TSAP 1522 на хосте 2 и ожидает входящего вызова. Вопрос о том, как процесс соединяется с TSAP, лежит за пределами сетевой модели и полностью зависит от локальной операционной системы. Например, может вызываться примитив типа LISTEN.

2. Прикладной процесс хоста 1 хочет отправить почтовое сообщение, поэтому он подключается к TSAP 1208 и обращается к сети с запросом CONNECT, указывая TSAP 1208 на хосте 1 в качестве адреса отправителя и TSAP 1522 на хосте 2 в качестве адреса получателя. Это действие в результате приводит к установке транспортного соединения между прикладным процессом и сервером.

3. Прикладной процесс отправляет почтовое сообщение.

4. Почтовый сервер отвечает, что сообщение будет доставлено.

5. Транспортное соединение разрывается.

Илл. 6.8. Точки доступа к службам транспортного и сетевого уровня и транспортные соединения

Обратите внимание, что на хосте 2 могут располагаться и другие серверы, соединенные со своими TSAP и ожидающие входящих запросов на соединение, приходящих с той же NSAP.

Это хорошая схема, только мы обошли стороной один маленький вопрос: как пользовательский процесс хоста 1 узнает, что почтовый сервер соединен с TSAP 1522? Возможно, почтовый сервер подключается к TSAP 1522 уже долгие годы, и постепенно об этом узнали все пользователи сети. В этом случае службы имеют постоянные TSAP-адреса, хранящиеся в файлах, которые расположены в известных местах. Так, например, в /etc/services UNIX-систем перечисляются серверы, за которыми закреплены конкретные порты, в частности, там указано, что почтовый сервер использует TCP порт 25.

Хотя постоянные TSAP-адреса хорошо подходят для небольшого количества никогда не меняющихся ключевых служб (например, для веб-сервера), зачастую пользовательские процессы хотят обменяться данными с другими процессами, TSAP-адреса которых заранее неизвестны или существуют только в течение короткого времени.

Чтобы справиться с этой ситуацией, можно использовать другую схему. В этой модели задействован специальный процесс: сопоставитель портов (portmapper). Чтобы найти TSAP-адрес, соответствующий заданному имени службы, например «BitTorrent», пользователь устанавливает соединение с сопоставителем портов (TSAP-адрес которого всем известен). Затем он отправляет сообщение с названием нужной ему службы, и сопоставитель портов сообщает ее TSAP-адрес. После этого пользователь разрывает соединение с сопоставителем портов и устанавливает соединение с этой службой.

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

Функция сопоставителя портов аналогична работе оператора телефонной справочной службы — он преобразует имена в номера. Здесь, как и в телефонной системе, важно, чтобы TSAP-адрес сопоставителя (или обрабатывающего сервера в протоколе начального соединения) был действительно хорошо известен. Если вы не знаете номера телефонной справочной службы, вы не сможете позвонить оператору. Если вам кажется, что номер справочной очевиден, попытайтесь угадать его, находясь в другой стране.

Многие существующие на компьютере серверные процессы используются редко. Поддерживать каждый из них в активном состоянии с постоянным TSAP-адресом слишком расточительно. Альтернативный вариант в упрощенном виде показан на илл. 6.9. Он называется протоколом начального соединения (initial connection protocol). Вместо того чтобы назначать всем возможным серверам хорошо известные TSAP-адреса, каждое устройство, желающее предоставлять службы удаленным пользователям, обзаводится специальным обрабатывающим сервером (process server), действующим как прокси (посредник) для менее активно используемых серверов. В UNIX-системах такой сервер называется inetd. Он прослушивает одновременно несколько портов, ожидая запроса на соединение. Потенциальные пользователи начинают с того, что отправляют запрос CONNECT, указывая TSAP-адрес нужной им службы. Если ни один сервер их не ждет, они получают соединение с обрабатывающим сервером (илл. 6.9 (а)).

Илл. 6.9. Пользовательский процесс хоста 1 устанавливает соединение с почтовым сервером хоста 2 через обрабатывающий сервер

Получив запрос, обрабатывающий сервер порождает подпроцесс запрошенного сервера, передавая ему существующее соединение с пользователем. Новый сервер выполняет нужную задачу, в то время как обрабатывающий сервер возвращается к ожиданию новых запросов (см. илл. 6.9 (б)). Этот метод работает только в тех случаях, когда серверы могут создаваться по требованию.


6.2.2. Установление соединения

Установление соединения звучит просто, но неожиданно оказывается весьма трудным делом. На первый взгляд достаточно, чтобы одна транспортная подсистема отправила адресату сегмент CONNECTION REQUEST и получила в ответ CONNECTION ACCEPTED. Неприятность заключается в том, что сеть может потерять, задержать, повредить или дублировать пакеты. Это может сильно осложнить ситуацию.


Проблема: задержка и дублирование пакетов

Представьте себе настолько перегруженную сеть, что подтверждения практически никогда не доходят вовремя, все пакеты опаздывают и пересылаются повторно по два-три или более раза. Предположим, что сеть основана на дейтаграммах и что каждый пакет следует по своему маршруту. Некоторые пакеты могут застрять в «пробке» и прийти с большим опозданием, когда отправитель уже решит, что они утеряны.

Самый кошмарный сценарий выглядит следующим образом. Пользователь устанавливает соединение с банком и отправляет сообщение с запросом о переводе крупной суммы на счет ненадежного человека. К несчастью, пакеты решают прогуляться по замысловатому маршруту, посетив самые отдаленные уголки сети. Тем временем отправитель вынужден выполнить повторную передачу. На этот раз пакеты идут по кратчайшему пути и доставляются быстро, в результате чего отправитель разрывает соединение.

Происходит очередная неудача: первая порция пакетов выходит из укрытия и добирается до адресата в нужном порядке, предлагая банку установить новое соединение и снова выполнить перевод. У банка нет способа определить, что это дубликаты. Он решает, что это вторая независимая транзакция, и еще раз переводит деньги.

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

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

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

Другой вариант — каждому соединению присваивается уникальный идентификатор (последовательный номер, возрастающий на единицу для каждого установленного соединения). Он выбирается инициатором соединения и помещается в каждый сегмент, включая тот, который содержит CONNECTION REQUEST. После разрыва соединения транспортная подсистема может обновить таблицу, в которой хранятся прежние соединения в виде пар (одноранговая транспортная подсистема, идентификатор). При каждом новом запросе таблица проверяется на наличие соответствующего идентификатора (он мог остаться там от разорванного ранее соединения).

К сожалению, у этой схемы есть существенный изъян: требуется, чтобы каждая транспортная подсистема (как отправитель, так и получатель) практически бесконечно хранила определенное количество информации об истории соединений. Иначе, если устройство выйдет из строя и потеряет данные, оно не сможет определить, какие соединения уже использовались, а какие нет.

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

Время жизни пакета может быть ограничено до известного максимума с помощью одного из следующих методов:


1. Проектирование сети с ограничениями.

2. Внедрение в каждый пакет счетчика транзитных участков.

3. Внедрение в каждый пакет временной метки.

К первому способу относятся все методы, предотвращающие зацикливание пакетов в комбинации с ограничением задержки, включая перегрузки по самому длинному возможному пути. Осуществить эту идею достаточно трудно, учитывая, что интерсеть может охватывать как один город, так и весь мир. Второй способ заключается в изначальной установке счетчика на определенное значение и уменьшении его на единицу на каждом маршрутизаторе. Сетевой протокол передачи данных просто игнорирует все пакеты, у которых значение счетчика достигло нуля. Третий способ состоит в том, что в каждом пакете указывается время его создания, а маршрутизаторы договариваются игнорировать все пакеты старше определенного времени. Для этого требуется синхронизация тактовых генераторов маршрутизаторов, что само по себе является нетривиальной задачей. На самом деле возраст пакета можно довольно точно вычислять с помощью счетчика транзитных участков.

На практике нужно гарантировать не только то, что пакета больше нет, но и что все его подтверждения также исчезли. Поэтому вводится период T, который в несколько раз превышает максимальное время жизни пакета (для каждой сети оно является константой и выбирается с запасом; для интернета оно составляет 120 с). На какое число умножается максимальное время жизни пакета, зависит от протокола, и это влияет только на длительность T. Если подождать в течение T с момента отправки пакета, то можно быть уверенными, что все следы этого пакета уничтожены и что он не возникнет вдруг как гром среди ясного неба.

При ограниченном времени жизни пакетов можно разработать надежный и практичный способ отвергать задержавшиеся дублированные сегменты. Автор описанного ниже метода — Томлинсон (Tomlinson, 1975); позднее он был улучшен Саншайном и Далалом (Sunshine and Dalal, 1978). Варианты этого метода широко используются на практике, и одним из примеров применения является TCP.

Основная идея метода заключается в том, что отправитель присваивает сегментам последовательные номера, которые не будут повторно использоваться в течение последующих T секунд. Размер такого номера складывается из периода T и скорости пакетов в секунду. Таким образом, в любой момент времени может отправляться только один пакет с данным номером. Копии этого пакета все равно могут появиться, и получатель должен их удалить.

Однако теперь невозможна ситуация, при которой задержавшийся дубликат принимается получателем вместо нового пакета с тем же порядковым номером.

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

Вместо этого Томлинсон предложил снабдить каждый хост генератором импульсов истинного времени. Генераторы разных хостов синхронизировать не обязательно. Предполагается, что они представляют собой двоичные счетчики, которые увеличиваются через равные интервалы времени. Кроме того, число разрядов счетчика должно равняться числу битов в последовательных номерах (или превосходить его). Последнее и самое важное предположение состоит в том, что таймер продолжает работать, даже если хост зависает.

При установке соединения младшие k-биты генераторов импульсов используются в качестве k-битного начального порядкового номера. Таким образом, в отличие от протоколов, описанных в главе 3, каждое соединение начинает нумерацию своих сегментов с разных чисел. Диапазон этих номеров должен быть достаточно большим, чтобы к тому моменту, когда порядковые номера сделают полный круг, старые сегменты с такими же номерами уже давно исчезли. Линейная зависимость порядковых номеров от времени представлена на илл. 6.10 (а). Запретная зона показывает, в какой момент времени определенные порядковые номера сегментов являются недействительными. При отправке сегмента с порядковым номером из этой зоны он может задержаться и сыграть роль другого пакета с таким же номером, который будет отправлен позже. К примеру, если хост выходит из строя и возобновляет работу в момент времени 70 с, он будет использовать начальные порядковые номера на основе генератора импульсов; хост не начинает отсчет с наименьшего порядкового номера в запретной зоне.

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

Чтобы порядковые номера пакетов не попадали в запретную зону, необходимо учесть следующее. Проблемы могут возникнуть по двум причинам. Если хост отправляет слишком быстро и слишком много данных, кривая используемых в действительности порядковых номеров может оказаться круче линии зависимости начальных номеров от времени. В результате порядковый номер попадет в запретную зону. Чтобы этого не произошло, скорость передачи данных в каждом открытом соединении должна быть ограничена одним сегментом за единицу времени. Кроме того, после восстановления транспортная подсистема не должна открывать новое соединение до появления нового тактового импульса, чтобы один и тот же номер не использовался дважды. Следовательно, интервал импульсов должен быть коротким (1 мкс или меньше). При этом генератор не должен работать слишком быстро (относительно порядковых номеров). Если частота импульсов равна C, а размер пространства порядковых номеров равен S, условие S/C > T является обязательным, чтобы номера не сделали полный круг слишком быстро.

Илл. 6.10. (а) Сегменты не могут заходить в запретную зону. (б) Проблема ресинхронизации

В запретную зону можно попасть не только снизу, передавая данные слишком быстро. Как видно из илл. 6.10 (б), при любой скорости передачи ниже скорости генератора импульсов кривая фактически используемых порядковых номеров попадет в запретную зону слева, когда порядковые номера пройдут по кругу. Чем круче наклон этой кривой, тем дольше придется ждать этого события. Чтобы избежать такой ситуации, можно ограничить скорость продвижения порядковых номеров (или время жизни соединения).

Метод, основанный на генераторе импульсов истинного времени, решает проблему распознавания опаздывающих дубликатов сегментов и новых сегментов. Однако использовать его для установления соединения может быть проблематично. Поскольку обычно получатель не помнит порядковые номера разных соединений, он не может узнать, является ли сегмент CONNECTION REQUEST, содержащий какой-либо начальный порядковый номер, дубликатом одного из предыдущих соединений. Во время соединения такой проблемы не возникает, поскольку протокол раздвижного окна знает текущий порядковый номер.


Решение: «тройное рукопожатие»

Для разрешения этой специфической проблемы Томлинсон (1975) предложил «тройное рукопожатие» (three-way handshake). Этот протокол установления соединения предполагает, что одна из сторон проверяет, является ли соединение все еще действующим. Нормальная процедура установления соединения показана на илл. 6.11 (а). Хост 1 выбирает порядковый номер x и отправляет сегмент CONNECTION REQUEST, содержащий этот номер, хосту 2. Хост 2 отвечает сегментом ACK, подтверждая x и объявляя свой начальный порядковый номер y. Наконец, хост 1 подтверждает выбранный хостом 2 номер в первом отправленном им информационном сегменте.

Теперь рассмотрим работу «тройного рукопожатия» при задержке дубликата управляющего сегмента. На илл. 6.11 (б) первый сегмент представляет собой задержавшийся дубликат CONNECTION REQUEST от старого соединения. Этот сегмент приходит на хост 2; хост 1 об этом не знает. Хост 2 реагирует на это отправкой хосту 1 сегмента ACK, таким образом запрашивая подтверждение того, что хост 1 действительно пытался установить новое соединение. Когда хост 1 отказывается это сделать (REJECT), хост 2 понимает, что он был обманут задержавшимся дубликатом, и прерывает соединение. В результате дубликат не причиняет вреда.

При наихудшем сценарии оба сегмента — CONNECTION REQUEST и ACK — блуждают по подсети. Этот случай показан на илл. 6.11 (в). Как и в предыдущем примере, хост 2 получает задержавшийся сегмент CONNECTION REQUEST и отвечает на него. Здесь нужно учитывать, что хост 2 предложил использовать y в качестве начального порядкового номера для трафика от хоста 2 к хосту 1, хорошо зная, что сегментов, содержащих порядковый номер y, или их подтверждений в данный момент в сети нет. Когда хост 2 получает второй задержавшийся сегмент, он понимает, что это дубликат, так как в этом модуле подтверждается не y, а z. Важно понимать, что не существует такой комбинации сегментов, которая заставила бы протокол ошибиться и случайно установить соединение, когда оно никому не нужно.

Илл. 6.11. Три сценария установления соединения с помощью «тройного рукопожатия» (CR означает CONNECTION REQUEST). (а) Нормальная работа. (б) Появление старого дубликата CR. (в) Дубликат CR и дубликат ACK

TCP всегда использует «тройное рукопожатие» для установления соединения. Внутри соединения к 32-битному порядковому номеру добавляется метка времени, чтобы он не мог использоваться повторно в течение максимального времени жизни пакета, даже если скорость соединения составляет несколько гигабитов в секунду. Этот механизм был добавлен в TCP для решения проблем, возникающих при использовании быстрых линий. Он описан в RFC 1323 и называется защитой от повторного использования порядковых номеров (Protection Against Wrapped Sequence numbers, PAWS). До появления PAWS при наличии нескольких соединений с начальными порядковыми номерами TCP применял метод генератора импульсов (см. выше). Однако этот метод оказался неэффективным с точки зрения безопасности. Злоумышленники могли легко угадать следующий начальный порядковый номер и инициировать ложное соединение, обманув схему «тройного рукопожатия». Поэтому на практике используются псевдослучайные начальные порядковые номера. Однако необходимо, чтобы эти номера, со стороны кажущиеся абсолютно случайными, не повторялись в течение определенного промежутка времени. Иначе задержавшиеся дубликаты могут вызвать серьезные неполадки в сети.


6.2.3. Разрыв соединения

Разорвать соединение проще, чем установить. Но здесь также имеются под­водные камни. Как уже было сказано, существует два стиля разрыва соединения: асимметричный и симметричный. Асимметричный разрыв связи соответствует принципу работы телефонной системы: когда одна из сторон вешает трубку, связь прерывается. При симметричном разрыве соединение рассматривается в виде двух отдельных однонаправленных связей, и требуется отдельное завершение каждого соединения.

Асимметричный разрыв связи является внезапным и может привести к потере данных. Рассмотрим сценарий на илл. 6.12. После установления соединения хост 1 отправляет сегмент, который успешно достигает хоста 2. Затем хост 1 передает другой сегмент. К несчастью, хост 2 отправляет DISCONNECTION REQUEST прежде, чем приходит второй сегмент. В результате соединение разрывается, а данные теряются.

Илл. 6.12. Внезапное разъединение с потерей данных

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

Симметричное разъединение подходит для тех случаев, когда у каждой стороны есть фиксированное количество данных для передачи и каждая из них точно знает, когда эти данные заканчиваются. В других случаях определить, что работа окончена и соединение может быть прервано, не так просто. Можно представить себе протокол, в котором хост 1 говорит: «Я закончил передачу. А вы?». Если хост 2 отвечает: «Я тоже. До свидания», соединение можно безо всякого риска разъединять.

К сожалению, этот протокол работает не всегда. Существует знаменитая проблема «двух армий» (two-army problem). Представьте, что армия белых находится в долине (илл. 6.13). На возвышенностях по обеим сторонам долины расположились две армии синих. Белая армия больше, чем любая из синих, но вместе синие превосходят белых. Если одна из армий синих атакует белых в одиночку, она потерпит поражение, но если синие сумеют атаковать белых одновременно, они могут победить.

Армии синих хотели бы синхронизировать свое выступление. Однако единственный способ связи состоит в отправке связного пешком по долине, где он может быть схвачен, а донесение потеряно (то есть приходится пользоваться ненадежным каналом). Спрашивается: существует ли протокол, позволяющий армиям синих победить?

Предположим, командир 1-й армии синих отправляет следующее сообщение: «Я предлагаю атаковать 29 марта, на рассвете. Сообщите ваше мнение». Теперь предположим, что сообщение успешно доставляется и что командир 2-й армии синих соглашается, а его ответ успешно доставляется обратно в 1-ю армию. Состоится ли атака? Вероятно, нет, так как командир 2-й армии не уверен, что его ответ получен. Если нет, то 1-я армия синих не будет атаковать, и было бы глупо с его стороны в одиночку ввязываться в сражение.

Теперь улучшим протокол с помощью «тройного рукопожатия». Инициатор оригинального предложения должен подтвердить ответ. Если исходить из предположения, что сообщения не теряются, то 2-я армия синих получит подтверждение, но теперь будет сомневаться командир 1-й армии синих. Он не знает, было ли доставлено его подтверждение, а ведь если оно не доставлено, то 2-я армия не будет атаковать. Протокол четырехкратного «рукопожатия» здесь также не поможет.

В действительности можно доказать, что протокола, решающего данную проблему, не существует. Предположим, что такой протокол все же есть. В этом случае последнее сообщение протокола либо является важным, либо нет. Если оно не является важным, удалим его (а также все остальные несущественные сообщения), пока не останется протокол, в котором все сообщения являются существенными. Что произойдет, если последнее сообщение не дойдет до адресата? Мы только что решили, что сообщение является важным, поэтому если оно потеряется, атака не состоится. Поскольку отправитель последнего сообщения никогда не сможет быть уверенным в его получении, он не станет рисковать. Другая синяя армия это знает и также воздержится от атаки.

Чтобы увидеть, какое отношение проблема «двух армий» имеет к разрыву соединения, просто замените слово «атаковать» на «разъединить». Если ни одна сторона не может разорвать соединение до тех пор, пока она не уверена, что другая сторона также готова к этому, то разъединения не произойдет никогда.

Илл. 6.13. Проблема «двух армий»

На практике, чтобы справиться с этой ситуацией, нужно отказаться от идеи договоренности и переложить ответственность на пользователей, чтобы каждая сторона принимала независимое решение о том, когда будет выполнена операция. Такую проблему решить проще. На илл. 6.14 показаны четыре сценария разъединения, использующих «тройное рукопожатие». Этот протокол не является безошибочным, но обычно он работает успешно.

На илл. 6.14 (а) показан нормальный случай, при котором пользователь отправляет DR (DISCONNECTION REQUEST), чтобы инициировать разрыв соединения. Когда запрос доставляется, получатель также отвечает сегментом DR и включает таймер на случай его потери. Когда запрос прибывает, первый пользователь отправляет в ответ на него сегмент ACK и разрывает соединение. Наконец, когда ACK приходит, получатель также разрывает соединение. Разъединение означает, что транспортная подсистема удаляет информацию об этой связи из своей таблицы открытых соединений и сигнализирует о разрыве владельцу соединения (потребителю транспортной службы). Эта процедура отличается от применения пользователем примитива DISCONNECT.

Если последний сегмент ACK теряется (илл. 6.14 (б)), ситуацию спасает таймер. Когда время истекает, соединение разрывается в любом случае.

Теперь рассмотрим случай потери второго DR. Пользователь, инициировавший разъединение, не получит ожидаемого ответа, у него истечет время ожидания, и он начнет все сначала. На илл. 6.14 (в) показано, как это происходит, если все последующие запросы и подтверждения успешно доходят до адресатов.

Последний сценарий (илл. 6.14 (г)) аналогичен предыдущему с одной лишь разницей: в этом случае предполагается, что все повторные попытки передать DR также терпят неудачу, поскольку все сегменты теряются. После N попыток отправитель, наконец, сдается и разрывает соединение. Тем временем у получателя также истекает время, и он тоже отсоединяется.

Хотя такого протокола обычно бывает вполне достаточно, теоретически он может ошибиться, если потеряются изначальный DR и все N повторных передач. Отправитель сдается и разрывает соединение, тогда как другая сторона ничего не знает о попытках разорвать связь и сохраняет активность. В результате получается полуоткрытое соединение, что недопустимо.

Этой ситуации можно избежать, если не позволить отправителю сдаваться после N повторных передач, а заставить его продолжать попытки, пока не будет получен ответ. Однако если другой стороне будет разрешено разрывать связь по таймеру, тогда отправитель действительно будет вечно повторять попытки, так как ответа он не получит никогда. Если же получателю также не разрешать разрывать соединение по таймеру, то протокол зависнет в ситуации, изображенной на илл. 6.14 (г).

Илл. 6.14. Четыре сценария разрыва соединения. (а) Нормальный случай «тройного рукопожатия». (б) Потеряно последнее подтверждение. (в) Потерян ответ. (г) Потерян ответ и последующие запросы разъединения

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

Для реализации этого правила каждая сторона должна иметь таймер, который перезапускается после отправки каждого сегмента. Если таймер срабатывает, передается пустой сегмент — чтобы другая сторона не отсоединилась. Но если применяется правило автоматического разъединения и передается слишком много пустых сегментов подряд, только чтобы линия не простаивала, то соединение автоматически разрывается сначала одной стороной, а затем и другой.

На этом мы заканчиваем обсуждение данного вопроса, но теперь должно быть ясно, что разорвать соединение без потери данных не так просто, как это кажется на первый взгляд. Из всего сказанного можно сделать вывод, что пользователь службы должен принимать участие в решении вопроса о разъединении — транспортная подсистема не может с этим справиться самостоятельно. Обратите внимание, что хотя TCP обычно использует симметричный разрыв связи (при этом каждая сторона независимо прерывает свое соединение, отправляя пакет FIN после окончания передачи данных), веб-серверы часто передают клиентам специальный пакет RST, сообщающий о мгновенном разрыве соединения, что больше похоже на асимметричный разрыв. Это возможно только потому, что веб-сервер знаком с процедурой обмена данными. Сначала он получает запрос от клиента (единственное, что отправляет клиент), а затем отсылает ему ответ.

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


6.2.4. Контроль ошибок и управление потоком данных

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

Оба этих вопроса мы уже обсуждали, когда говорили о канальном уровне в главе 3. На транспортном уровне используются те же механизмы. Вкратце они выглядят так.


1. Фрейм содержит код с обнаружением ошибок (например, CRC-код или контрольную сумму), с помощью которого проверяется, правильно ли была доставлена информация.

2. Фрейм содержит идентифицирующий порядковый номер и передается отправителем до тех пор, пока не придет подтверждение об успешной доставке. Это называется автоматическим запросом повторной передачи (Automatic Repeat reQuest, ARQ).

3. Число фреймов, передаваемых отправителем в любой момент времени, обычно ограниченно: передача приостанавливается, если подтверждения приходят недостаточно быстро. Если этот максимум равен одному пакету, протокол называется протоколом с остановкой и ожиданием подтверждения (stop-and-wait). Окна большего размера позволяют использовать конвейерную обработку, а также улучшить производительность при работе с длинными и быстрыми линиями.

4. Протокол раздвижного окна (sliding window) сочетает в себе все эти возможности, а также поддерживает двунаправленную передачу данных.

Если эти механизмы применяются к фреймам на канальном уровне, то возникает логичный вопрос: как это можно перенести на сегменты транспортного уровня? На практике выясняется, что эти уровни во многом копируют друг друга. Но хотя к ним и применимы одинаковые механизмы, существуют различия в их функционировании и качестве.

Чтобы проиллюстрировать функциональное различие, обратимся к обнаружению ошибок. Контрольная сумма на канальном уровне защищает фрейм, пока он передается по каналу. На транспортном уровне она защищает сегмент на всем протяжении пути. Это сквозная проверка, которая отличается от проверки на каждом канале. Существуют примеры повреждения пакетов даже внутри маршрутизаторов (Зальцер и др.; Saltzer et al., 1984). Контрольные суммы канального уровня обеспечивали защиту пакетов, пока они передвигались по каналу, но не тогда, когда они были внутри маршрутизатора. В результате мы имеем дело с некорректной доставкой, хотя проверка на каждом канале не выявила ошибок.

Этот и другие примеры позволили Зальцеру и др. сформулировать «сквозной» принцип (end-to-end argument). Согласно этому принципу, сквозная проверка на транспортном уровне необходима для корректной передачи данных; проверка на канальном уровне не является необходимой, но позволяет существенно улучшить производительность (так как иначе поврежденный пакет будет все равно проходить весь путь, что приведет к лишней нагрузке на сеть).

Чтобы продемонстрировать разницу в качестве, рассмотрим повторную передачу данных и протокол раздвижного окна. Большинство беспроводных каналов, в отличие от спутниковых, позволяют отправлять только один фрейм за единицу времени. Это значит, что произведение пропускной способности и времени задержки для канала достаточно мало и внутри канала едва ли может поместиться целый фрейм. Для лучшей производительности следует использовать окно маленького размера. К примеру, стандарт 802.11 применяет протокол с остановкой и ожиданием подтверждения. Он выполняет передачу и повторные передачи одного фрейма до тех пор, пока не придет подтверждение о его получении, и только после этого переходит к другому фрейму. Если бы размер окна был больше одного фрейма, это не повысило бы производительность, а только усложнило процесс передачи. В проводных и оптоволоконных линиях связи, таких как Ethernet (коммутируемый) и магистрали интернет-провайдеров, частота появления ошибок невелика. Это позволяет отказаться от повторных передач на канальном уровне, так как небольшое количество потерянных фреймов может быть восстановлено с помощью повторных сквозных передач.

С другой стороны, для многих TCP-соединений произведение пропускной способности и времени задержки составляет гораздо больше, чем один сегмент. Рассмотрим соединение, которое передает данные по всей территории США со скоростью 1 Мбит/с, а время, за которое пакет доходит до получателя и обратно, составляет 200 мс. Даже при таком медленном соединении 200 Кбит информации будут храниться на стороне получателе столько, сколько требуется для отправки сегмента и получения подтверждения. В таких случаях следует использовать раздвижное окно большого размера. Протокол с остановкой и ожиданием подтверждения серьезно навредит производительности. В нашем примере он ограничил бы передачу одним сегментом в 200 мс (это 5 сегментов/с) независимо от реальной скорости сети.

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

Зная, что отправитель выполняет буферизацию, получатель может использовать свои буферы как посчитает нужным. Например, он может содержать единый буферный накопитель для всех соединений. Когда приходит сегмент, предпринимается попытка динамически выделить ему новый буфер. Если это удается, то сегмент принимается, в противном случае он отвергается. Отправитель готов к тому, чтобы передавать потерянные сегменты повторно, и это игнорирование сегментов не наносит большого вреда, хотя расходует некоторые ресурсы. Отправитель просто повторяет попытки до тех пор, пока не получит подтверждение.

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

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

Илл. 6.15. Организация набора буферов. (а) Цепочка буферов фиксированного размера. (б) Цепочка буферов переменного размера. (в) Один большой циклический буфер для одного соединения

Другой способ решения проблемы состоит в использовании буферов переменного размера (илл. 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. В результате он не отправит сегмент повторно, так как будет считать, что сегмент уже получен, что приведет к потере сегмента.

Должно быть, вы подумали: «А что, если поменять местами последовательность действий транспортной подсистемы сервера, чтобы сначала осуществлялась запись, а потом высылалось подтверждение?» Представим, что запись сделана, но сбой произошел до отправки подтверждения. Тогда клиент окажется в состоянии S1 и передаст сегмент повторно, а мы получим дубликат сегмента в выходном потоке.

Таким образом, независимо от того, как запрограммированы клиент и сервер, всегда может возникнуть ситуация, в которой протокол не сможет правильно восстановиться. Сервер можно запрограммировать двумя способами: так, чтобы он в первую очередь высылал подтверждение, или так, чтобы он сначала записывал сегмент. Клиент может быть запрограммирован одним из четырех способов: всегда повторно передавать последний сегмент; никогда не передавать повторно последний сегмент; передавать сегмент повторно только в состоянии S0; передавать сегмент повторно только в состоянии S1. Таким образом, получаем восемь комбинаций, но как будет показано далее, для каждой комбинации имеется набор событий, ведущий к ошибке протокола.

На сервере могут происходить три события: отправка подтверждения (A), запись сегмента в выходной процесс (W) и сбой (C). Они могут произойти в виде шести возможных последовательностей: AC(W), AWC, C(AW), C(WA), WAC и WC(A), где скобки означают, что после C события A или W уже не произойдут (ведь уже случился сбой). На илл. 6.18 показаны все восемь комбинаций стратегий сервера и клиента, каждая со своими последовательностями событий. Обратите внимание, что для каждой комбинации существует последовательность, приводящая к ошибке протокола. Например, если клиент всегда передает повторно неподтвержденный сегмент, событие AWC приведет к появлению неопознанного дубликата, хотя при двух других последовательностях протокол будет работать правильно.

Илл. 6.18. Различные комбинации стратегий сервера и клиента

Усложнение протокола не поможет. Даже если клиент и сервер обменяются несколькими сегментами, прежде чем сервер попытается осуществить запись, и клиент будет точно знать, что происходит, у него нет возможности определить, когда произошел сбой: до или после записи. Отсюда следует неизбежный вывод: при жестком условии отсутствия одновременных событий (то есть если отдельные события происходят друг за другом, а не одновременно) более высокие уровни не могут отследить сбой и восстановление хоста.

Все это можно обобщить следующим образом: восстановление после сбоя уровня N может быть осуществлено только уровнем N + 1 и только при условии, что на более высоком уровне сохраняется информация о процессе, достаточная для возвращения в прежнее состояние. Это согласуется с утверждением о том, что транспортный уровень может обеспечить восстановление от сбоя на сетевом уровне, если каждая сторона соединения отслеживает свое текущее состояние.

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

Даже в этом случае получение подтверждения устройством пользователя не означает, что удаленный хост успел обновить базу данных. Похоже, что настоящее сквозное подтверждение, получение которого означает, что работа сделана (а отсутствие означает обратное), невозможно. Более подробно этот вопрос обсуждается у Зальцера и др. (Saltzer et al., 1984).

Загрузка...