Тип frame содержит четыре поля: kind, seq, ack и info; первые три содержат управляющую информацию, а последнее может включать данные, которые необходимо передать. Три управляющих поля вместе называются заголовком фрейма (frame header).
Поле kind сообщает о наличии данных внутри фрейма, так как некоторые протоколы отличают фреймы, содержащие только управляющую информацию, от фреймов, в которых также есть данные. Поле seq используется для хранения последовательного номера фрейма, ack — для подтверждения. Подробнее их применение будет описано ниже.
Поле данных фрейма info содержит один пакет. В управляющем фрейме это поле не задействуется. На практике используется поле info переменной длины, в управляющих фреймах оно полностью отсутствует.
Важно понимать взаимоотношения между пакетом и фреймом (см. илл. 3.1). Сетевой уровень создает пакет, принимая сообщение от транспортного уровня и добавляя к нему свой заголовок. Пакет передается канальному уровню, который включает его в поле info исходящего фрейма. Когда целевое устройство получает фрейм, канальный уровень извлекает пакет из фрейма и передает его сетевому. Таким образом, сетевой уровень может действовать так, будто устройства обмениваются пакетами напрямую.
#define MAX_PKT 1024 /* определяет размер пакета в байтах
typedef enum {false, true} boolean; /* тип boolean */
typedef unsigned int seq_nr; /* порядковые номера фреймов или подтверждений */
typedef struct {unsigned char data[MAX_PKT];} packet; /* определение пакета */
typedef enum {data, ack, nak} frame_kind; /* определение типа фрейма */
typedef struct { /* фреймы, транспортируемые на данном уровне */
frame_kind kind; /* тип фрейма */
seq_nr seq; /* порядковый номер */
seq_nr ack; /* номер подтверждения */
packet info; /* пакет сетевого уровня */
} frame;
/* Ожидать события и вернуть тип события в переменной event. */
void wait_for_event(event_type *event);
/* Получить пакет у сетевого уровня для передачи по каналу. */
void from_network_layer(packet *p);
/* Передать информацию из полученного пакета сетевому уровню. */
void to_network_layer(packet *p);
/* Получить пришедший пакет у физического уровня и скопировать его в r. */
void from_physical_layer(frame *r);
/* Передать фрейм физическому уровню для отправки. */
void to_physical_layer(frame *s);
/* Запустить таймер и разрешить событие timeout. */
void start_timer(seq_nr k);
/* Остановить таймер и запретить событие timeout. */
void stop_timer(seq_nr k);
/* Запустить вспомогательный таймер и разрешить событие ack_timeout. */
void start_ack_timer(void);
/* Остановить вспомогательный таймер и запретить событие ack_timeout. */
void stop_ack_timer(void);
/* Разрешить сетевому уровню инициировать событие network_layer_ready. */
void enable_network_layer(void);
/* Запретить сетевому уровню инициировать событие network_layer_ready. */
void disable_network_layer(void);
/* Макрос inc развертывается прямо в строке: циклически увеличить переменную k. */
#define inc(k) if (k < MAX_SEQ) k = k + 1; else k = 0
Илл. 3.11. Общие определения для последующих протоколов. Определения располагаются в файле protocol.h
На илл. 3.11 также перечислен ряд процедур. Это библиотечные процедуры, детали которых зависят от конкретной реализации; их внутреннее устройство мы рассматривать не будем. Как уже упоминалось ранее, процедура wait_for_event представляет собой холостой цикл ожидания какого-либо события. Процедура to_network_layer используется канальным уровнем для отправки пакетов сетевому, from_network_layer — для получения пакетов от него. Обратите внимание: процедуры from_physical_layer и to_physical_layer служат для обмена фреймами между канальным и физическим уровнями, тогда как процедуры to_network_layer и from_network_layer применяются для передачи пакетов между канальным и сетевым уровнями. Другими словами, процедуры to_network_layer и from_network_layer относятся к интерфейсу между уровнями 2 и 3, а процедуры from_physical_layer и to_physical_layer — к интерфейсу между уровнями 1 и 2.
В большинстве протоколов предполагается использование ненадежного канала, который может случайно потерять целый фрейм. Чтобы избежать неприятных последствий, при отправке фрейма передающий канальный уровень запускает таймер. Если за установленный интервал времени ответ не получен, срок ожидания истекает и канальный уровень получает сигнал прерывания.
В приведенных здесь протоколах этот сигнал реализован в виде значения event=timeout, возвращаемого процедурой wait_for_event. Для запуска и остановки таймера используются процедуры start_timer и stop_timer соответственно. Событие timeout может произойти, только если был запущен таймер, но еще не была вызвана процедура stop_timer. Процедуру start_timer разрешается запускать во время работающего таймера. Такой вызов просто перезапускает таймер, и отсчет начинается заново (до нового перезапуска или выключения).
Процедуры start_ack_timer и stop_ack_timer запускают и останавливают вспомогательные таймеры, используемые для создания подтверждений в некоторых ситуациях.
Процедуры enable_network_layer и disable_network_layer применяются в более сложных протоколах, где уже не предполагается, что у сетевого уровня всегда есть пакеты для отправки. Когда канальный уровень разрешает работу сетевого, последний может посылать сигнал прерывания, когда ему нужно передать пакет. Такое событие обозначается как event = network_layer_ready. Когда сетевой уровень отключен, он не может инициировать такие события. Канальный уровень тщательно следит за включением и выключением сетевого и не допускает ситуации, когда тот заваливает его пакетами, для которых нет места в буфере.
Последовательные номера фреймов всегда находятся в пределах от 0 до MAX_SEQ включительно. Число MAX_SEQ отличается в разных протоколах. Для увеличения последовательного номера фреймов на 1 циклически (то есть с обнулением при достижении числа MAX_SEQ) используется макрос inc. Он определен в виде макроса, поскольку используется прямо в строке в тех местах программы, где быстродействие является критичным. Как мы увидим далее, производительность сети часто ограничена скоростью выполнения протоколов. Определение простых операций в виде макросов (а не процедур) не снижает удобочитаемости программы, увеличивая при этом ее быстродействие.
Объявления на илл. 3.11 входят во все последующие протоколы. В целях экономии места и для наглядности они были извлечены и собраны вместе, но, по сути, они должны быть объединены с протоколами. В языке C такое объединение производится путем размещения определений в специальном файле заголовка (в данном случае protocol.h) и включения их в файлы протокола с помощью #include — директивы препроцессора C.
3.3.3. Симплексные протоколы канального уровня
В данном разделе мы рассмотрим три простых протокола, из них каждый следующий способен справиться с более реалистичной ситуацией.
Протокол «Утопия»: без управления потоком и без исправления ошибок
В качестве первого примера мы рассмотрим самый простой протокол. Данные передаются только в одном направлении, а опасений, что где-то может произойти ошибка, даже не возникает. Сетевые уровни передающего и целевого устройств находятся в состоянии постоянной готовности. Время обработки минимально, размер буфера неограничен. А главное, линия связи между канальными уровнями никогда не теряет и не искажает фреймы. Этот совершенно нереальный протокол под названием «Утопия» показан на илл. 3.12. Он всего лишь демонстрирует базовую структуру, необходимую для построения настоящего протокола.