Контроль потока
Как говорилось ранее, на обоих хостах, между которыми установлено ТСР-соеди-нение, имеются приемные буферы. Протокол TCP помещает в приемные буферы байты, принятые без искажений и в правильном порядке. Приложение, связанное с TCP-соединением, считывает данные из приемного буфера в произвольные моменты времени, не зависящие от поступления новых данных. К примеру, приложение может быть занято выполнением трудоемких операций и обращаться к буферу со значительными задержками. Если частота поступления данных в буфер превышает частоту их считывания приложением, то через некоторый интервал времени возникает угроза переполнения буфера.
Для того чтобы избежать переполнения приемного буфера, протокол TCP предоставляет приложениям службу контроля потока, призванную контролировать, чтобы частота передачи данных соответствовала частоте их считывания принимающим приложением. Как уже упоминалось, скорость передачи данных может быть принудительно снижена при наличии перегрузок в сети; механизм, реализующий эту операцию, называется механизмом контроля перегрузок и детально рассматривается в разделах «Принципы контролирования перегрузки» и «Контроль перегрузок в ТСР». Несмотря на то что контроль потока и контроль перегрузок решают одну и ту же задачу (снижение скорости передачи), причины, вызывающие их выполнение, не имеют ничего общего. К сожалению, авторы многих книг употребляют эти два термина как синонимы, оставляя сообразительным читателям возможность самим «почувствовать разницу». Ниже мы увидим, каким образом TCP осуществляет контроль потока. Для того чтобы сосредоточить максимум внимания на концепции механизма, будем считать, что рассматриваемая реализация TCP игнорирует сегменты, нарушающие порядок следования данных.
Протокол TCP обеспечивает контроль потока с помощью переменной, называемой окном приема. Говоря неформальным языком, окно приема используется для оповещения передающей стороны об объеме свободного места в буфере принимающей стороны. Поскольку протокол TCP осуществляет дуплексную передачу данных, окно приема поддерживается обеими сторонами соединения. Рассмотрим понятие окна приема на примере передачи файла. Пусть хост А пересылает файл большого размера хосту В при помощи TCP-соединения. Хост В выделяет буфер для этого соединения, а процесс, которому адресован файл, периодически считывает данные из буфера. Размер буфера хранится в переменной RcvBuffer. Определим следующие переменные
□ LastByteRead — номер последнего байта потока данных, считанного из буфера прикладным процессом хоста В;
□ LastByteRcvd — номер последнего байта потока данных, полученного от передающей стороны и помещенного в буфер.
Чтобы входной буфер не мог переполниться, необходимо соблюдать неравенство
LastByteRcvd — LastByteRead <= RcvBuffer.
Значение окна приема RcvWindow равно объему свободного места в буфере:
RcvWindow = RcvBuffer — [LastByteRcvd — LastByteRead].
Поскольку объем свободного места в буфере не постоянен, значение переменной RcvWindow также динамически меняется. Сказанное иллюстрирует рис. 3.33.
Каким образом соединение использует переменную RcvWindow для реализации службы контроля потока? Хост В «говорит» хосту А о том, сколько свободного места имеется в его приемном буфере, помещая значение переменной RcvWindow в поле окна приема каждого сегмента, отправляемого хосту А. Начальное значение RcvWindow равно RcvBuffer. Для того чтобы этот механизм работал, необходимо поддерживать на хосте В несколько переменных, характеризующих данное ТСР-соединение.
Хост А поддерживает две переменные, LastByteSent и LastByteAcked, хранящие номера последнего отправленного и последнего квитированного байтов соответственно. Разность LastByteSent — LastByteAcked представляет размер неподтвержденных данных, переданных от хоста А хосту В. Поддерживая значение этой разности меньшим, чем RcvWindow, хост А может гарантировать, что приемный буфер хоста В не переполнится. Таким образом, на протяжении жизни соединения хост А должен соблюдать неравенство:
LastByteSent — LastByteAcked <= RcvWindow
Приведенной схеме присуща небольшая проблема. Предположим, что приемный буфер хоста В оказался заполненным, то есть значение RcvWindow стало равно 0. Возможно, что в этот момент хост В не имеет данных для передачи хосту А. Обратите внимание на то, что считывание приложением данных из буфера, приводящее к изменению значения RcvWindow, не влечет за собой генерацию пакета с новым значением RcvWindow. Напротив, TCP отсылает RcvWindow хосту А только вместе с данными или квитанциями. Таким образом, хост А своевременно не получит сведений об освободившемся пространстве буфера и не сможет осуществлять дальнейшую передачу данных. Чтобы решить эту проблему, спецификация TCP предусматривает периодическую генерацию хостом А сегментов, содержащих один байт данных, если окно приема хоста В имеет нулевое значение. Эти сегменты квитируются принимающей стороной, и при освобождении места в буфере ненулевое значение RcvWindow отправляется вместе с одной из квитанций.
Функционирование окна приема протокола TCP наглядно проиллюстрировано с помощью Java-апплета, находящегося на нашем сайте _http://www.awl.com/kurose-ross.
Отметим, что протокол UDP, в отличие от TCP, не обеспечивает контроля потока. Представьте себе описанную выше ситуацию, предположив, что вместо протокола TCP приложение использует UDP. Обычно протокол UDP помещает принятые данные в буфер, из которого они извлекаются прикладным процессом через сокет. Если считывание данных из буфера происходит медленнее, чем поступление новых данных, то буфер переполняется и происходит потеря части сегментов.