tanenbaum_seti_all.pages (525408), страница 156
Текст из файла (страница 156)
Таким образом, независимо от того, как запрограммированы клиснт и сервер, всегда могут быть ситуации, в которых протокол не сможет правильно восстановиться. Сервер можно запрограммировать двумя способами; так, чтобы он сначала передавал подтверждение, или так, чтобы сначала записывал ТРО()-модуль.
Клиент может быть запрограммирован одним из четырех способов: всегда передавать повторно последний ТР1)1)-модуль, никогда нс передавать повторно последний ТРО()-модуль, передавать повторно ТРР()-модуль только в состоянии 50 и персдавать повторно ТРЕН)-модуль только в состоянии 51. Таким образом, получаем восемь комбинаций, но, как будет показано, для каждой комбинации имеется набор событий, заставляющий протокол ошибиться.
На сервере могут происходить три события; отправка подтверждения (А), запись ТР1Н1-модуля в выходной процесс (Ю) и сбой (С). Они могут произойти в виде шести возможных последовательностей: АС(И), АИС, С(АЬ), С(ИА), ИАС и И'С(А), где скобки означают, что после события С событие А или В может и не произойти (то есть уж сломался — так сломался). На рис.
6.15 показаны все восемь комбинаций стратегий сервера и клиента, каждая со своими последовательностями событий. Обратите внимание па то, что для каждой комбинации сушествует последовательность собьп ий, приводящая к ошибке протокола. Например, если клиент всегда передает повторно пеподтвержденный ТРР()-модуль, событие А И~С приведет к появлению неопознанного дубликата, хотя при двух других последовательностях событий протокол будст работать правильно. Усложнение протокола не помогает. Даже если клиент и сервер обменяются несколькими ТР1)()-модулями, прежде чем сервер попытается записать полученный пакет, так что клиент будет точно знать, что происходит на сервере, у него нет возможности определить, когда произошел сбой на сервере: до или после записи. Отсюда следует неизбежный вывод: невозможно сделать отказ и восстановление хоста прозрачными для более высоких уровней.
В более общем виде это может быть сформулировано следующим образом: восстановление от сбоя уровня М может быть осуществлено только уровнем йГ+ 1 и только при условии, что на более высоком уровне сохраняется достаточное количество информации о состоянии процесса. Как упоминалось ранее, транспортный уровень может обеспечить восстановление от сбоя на сетевом уровне, если каждая сторона соединения отслеживает свос тскушсс состояние. Эта проблема подводит нас к вопросу о значении так называемого сквозного подтверждения. В принципе, транспортный протокол является сквозным, а не цепным, как более низкие уровни. Теперь рассмотрим случай обращения пользо- Простой транспортный протокол 585 вателя к удаленной базе данных.
Предположим, что удаленная транспортная сущность запрограммирована сначала передавать ТРП()-модуль вышестоящему уровню, а затем отправлять подтверждение. Даже в этом случае получение подтверждения машиной пользователя пе означает, что удаленный хост успел обновить базу данных. Настоящее сквозное подтверждение, получение которого означает, что работа была сделана, и, соответственно, отсутствие которого означает обратное, вероятно, невозможно.
Более подробно этот вопрос обсуждается в (За)бхег и др., 1984) Стратегия, используемая получающим хостом Сначала подтверждение, лотом запись Сначала зались, лотом подтверждение Стратегия, используемая передающим хостом АС(ИГ) АИГС С(АИг) С(ИГА) ИГАС ИГС(А) ОК = Протокол работает корректно 00Р = Протокол формирует дубликат сообщения СОЗТ = Протокол теряет сообщение Рис. В.тб.
Различные комбинации стратегий сервера и клиента Простой транспортный протокол Чтобы конкретизировать обсуждавшиеся ранее идеи, в данном разделе мы подробно изучим пример реализации транспортного уровня. В качестве абстрактных служебных примитивов будуг использоваться ориентированные на соединение примитивы из табл, 6.1. Такие примитивы были выбраны, чтобы сделать пример похожим (с некоторыми упрощениями) на популярный протокол ТСР. Слумсебные примитивы примера транспортного протокола Первая наша задача будет состоять в том, чтобы как можно более конкретно представить примитивы.
С примитивом СОййССТ (сосдинить) все довольно просто: у нас просто будет библиотечная процедура соллест, которую можно вызывать с соответствующими параметрами для установки соединения. Параметрами этой процедуры являются локальный и удаленный ТБАР-адреса. Программа, обра- 586 Глава 6. Транспортный уровень щающаяся к этой процедуре, блокируется (то есть приостанавливается) на время, пока транспортная сущность пытается установить соединение. Если установка соединения проходит успешно, программа разблокируется и может начинать передавать данные.
Когда процесс желает принимать входящие звонки, он обращается к процедуре 1)зЬеп (ожидать), указывая ТИАР-адрес, соединение с которым ожидается, При этом процесс блокируется, пока какой-либо удаленный процесс не попытается установить соелинение с его ТИАР-адресом. Обратите внимание: такая модель обладает сильной асимметрией.
Пассивная сторона выполняет процедуру 1)з1еп и ждет какого-либо события. Активная сторона инициирует соединение. Возникает интересный вопрос: что делать, если активная сторона начнет первой7 Первая стратегия такова: при отсутствии ожидания на пассивной стороне попытка соединения считается неудачной. Другая стратегия заключается в блокировании инициатора (возможно, навсегда), пока на противоположном конце устанавливаемого соединения процесс не перейдет в режим ожидания. Компромиссное решение, используемое в нашем примере, состоит в том, что на установку соединения процедуре соппес1 отводится определенный интервал времени. Если процесс хоста, с которым пытаются установить связь, вызовет процедуру 1)зсеп прежде, чем истечет интервал ожидания, соединение будет установлено, В противном случае звонящий получает отказ, разблокируется и получает сообщение об ошибке.
Для разрыва соединения мы будем применять процедуру Ейзсоппест. Соединение будет считаться разорванным, когда обе стороны вызовут зту процедуру. Другими словами, мы используем симметричную модель разъединения. При передаче данных появляется та же проблема, что и при установлении соединения: передатчик активен, а получатель пассивен. Мы будем использовать при передаче данных то же решение, что и при установке соединения: активную процедуру зеп0, передающую данные, и пассивную процедуру гесе1 те, блокирующую процесс до тех пор, пока не прибудет ТР1Н)-модуль.
Таким образом, наша услуга определяется пятью примитивами: СОММЕСТ, С!5ТЕМ, 015СОММЕСТ, 5ЕМО и МЕСЕ! ЧЕ. Каждому примитиву соответствует библиотечная служба, выполняющая примитив. Параметры для служебных примитивов и библиотечных процедур следующие: соплов - С!5ТЕМ 11оса1) сопппа - сОммест <1оса1. гееоге) зсагиа - 5ЕМО (сопппв. Ьиттег, Ьугез) атаюз - МЕСЕ1ЧЕ 1соппие.
Ьиттег. Ьугеа) аааЫа - 015СОММЕСТ (соплив) Примитив С15ТЕМ объявляет о желании обращающейся к нему стороны принимать запросы соединения, обращенные к указанному ТИАР-адресу. Пользователь примитива блокируется до тех пор, пока кто-либо не попытается с ним связаться. Понятия тайм-аута здесь нет. Примитив СОММЕСТ имеет на входе два параметра: локальный ТИАР-адрес 1оса1 и удаленный ТИАР-адрес гевоге. Он пытается установить транспортное соеднне- Простой транспортный протокол 587 ние между ними.
Если это удается, он возвращает в качестве выходного параметра соппэя неотрицательное число, используемое для идентификации соединения при следующих вызовах процедур, Если же установить соединение не удалось, то причина неудачи помещается в саппвя в виде отрицательного числа. В нашей простой модели каждый ТИАР-адрес может участвовать только в одном транспортном соединении, поэтому возможной причиной отказа может быть занятость одного из транспортных адресов. Среди других причин могут быть следуюшие; удаленный хост выключен, неверен локальный адрес или неверен удаленный адрес.
Примитив 5Ей0 передает содержимое буфера в виде сообщения по указанному транспортному соединению — может быть, в несколько приемов, если сообшение слишком велико. Возможные ошибки, возвращаемые в виде значения переменной згагсз, таковы: нет соединения, неверный адрес буфера или отрицательное число байт. Примитив кЕСЕ1ЧЕ означает готовность вызывающего его процесса принимать данные. Размер полученного сообщения помещается в переменную Ьугек Если удаленный процесс разорвал соединение или алрес буфера указан неверно (например, за пределами программы пользователя), переменной згзГоз присваивается значение кода ошибки, указывающего на причину возникновения проблемы. Примитив 015СОИкЕСТ разрывает транспортное соединение.