Каким способом передается информация канал

Основы надежной передачи данных

Тем, кто стремится разобраться в сетях и протоколах, посвящается.

В статье рассматриваются основы надежной передачи данных, реализуются примеры на Go, в том числе UDP и TCP . По мотивам раз, два, три и книги «Компьютерные сети. Нисходящий подход», а то все обсуждают только Танненбаума и Олиферов.

Протокол транспортного уровня

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

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

Это делается разбиением (при необходимости) сообщений прикладного уровня на фрагменты и добавлением к каждому из них заголовка транспортного уровня.

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

Принципы надежной передачи данных

Надежная передача данных по совершенно надежному каналу

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

Надежная передача данных по каналу с возможными ошибками

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

В таком случае применяются механизмы:

  • обнаружения ошибки;
  • обратной связи;
  • повторной передачи.

Протоколы надежной передачи данных, обладающие подобными механизмами многократного повторения передачи, называются протоколами с автоматическим запросом повторной передачи (Automatic Repeat reQuest, ARQ).
Дополнительно, стоит предусмотреть возможность ошибок и в квитанциях, когда принимающая сторона не получит никакой информации о результатах передачи последнего пакета.
Решение этой задачи, используемое в том числе в TCP, состоит в добавлении в пакет данных нового поля, содержащего порядковый номер пакета.

Надежная передача данных по ненадежному каналу, допускающему искажение и потерю пакетов

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

  • определения факта потери пакетов;
  • повторной доставки потерянных пакетов принимающей стороне.

Дополнительно, кроме потери пакета, необходимо предусмотреть возможность потери квитанции или, если ничего не потеряно, ее доставки со значительной задержкой. Во всех случаях производится одно и то же: повторная передача пакета. Для контролирования времени в данном механизме используется таймер отсчета, который позволяет определить окончание интервала ожидания. Так в пакете net параметр TCPKeepAlive установлен на 15 секунд по умолчанию:

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

Итак, мы ознакомились с ключевыми понятиями протоколов надежной передачи данных:

  • контрольными суммами;
  • порядковыми номерами пакетов;
  • таймерами;
  • положительными и отрицательными квитанциями.

Но и это не все!

Протокол надежной передачи данных с конвейеризацией

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

Применение конвейеризации приводит к:

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

Диапазон порядковых номеров и требования к размерам буферов зависят от действий, предпринимаемых протоколом в ответ на искажение, потерю и задержку пакета. В случае конвейеризации существуют два метода исправления ошибок:

  • возвращение на N пакетов назад;
  • выборочное повторение.

Возвращение на N пакетов назад — протокол скользящего окна

Отправитель должен поддерживать три типа событий:

  • вызов протоколом более высокого уровня. Когда «сверху» вызывается функция отправки данных, передающая сторона сначала проверяет степень заполнения окна (то есть наличие N посланных сообщений, ожидающих получения квитанций). Если окно оказывается незаполненным, новый пакет формируется и передается, а значения переменных обновляются. В противном случае передающая сторона возвращает данные верхнему уровню, и это является неявным указанием, что окно заполнено. Обычно верхний уровень предпринимает повторную попытку передачи данных через некоторое время. В реальном приложении отправитель, скорее всего, либо буферизовал бы данные (вместо немедленной отсылки), либо имел механизм синхронизации (например, семафор или флаг), который позволял бы вышестоящему уровню вызывать функцию отправки данных только при незаполненном окне.
  • получение подтверждения. В протоколе для пакета с порядковым номером N выдается общая квитанция, указывающая на то, что все пакеты с порядковыми номерами, предшествующими N, успешно приняты.
  • истечение интервала ожидания. Для определения фактов потерь и задержек пакетов и квитанций протокол использует таймер. Если интервал ожидания истекает, передающая сторона повторно отправляет все посланные неподтвержденные пакеты.
Читайте также:  Способов получения нерастворимых гидроксидов взаимодействие растворимых

Выборочное повторение

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

Пример

Лучшие теоритические практики собраны в практической реализации TCP. А если кто-то знает, как лучше — welcome.

Источник

Вся правда об ОСРВ. Статья #25. Каналы передачи данных: введение и базовые службы

После небольшого перерыва мы продолжаем публиковать переводы статей «Вся правда об ОСРВ» Колина Уоллса. В этот раз поговорим о каналах передачи данных (далее – каналы), которые были уже были упомянуты в одной из предыдущих статей, а именно в #5. Каналы, по сравнению с почтовыми ящиками (#21 и #22) или очередями (#23 и #24), предоставляют более гибкий способ передачи простых сообщений между задачами.

Предыдущие статьи серии:

Использование каналов

В Nucleus SE каналы определяются на этапе сборки. В каждом приложении может быть до 16 каналов. Если в приложении не сконфигурирован ни один канал, ни структуры данных, ни код служебных вызовов, относящиеся к каналам, не будут включены в приложение.

Канал передачи данных – набор хранилищ, размер каждого из которых позволяет размещать один элемент данных заданной пользователем длины в байтах. Доступ к данным контролируется таким образом, чтобы ими могли безопасно пользоваться несколько задач. Задачи могут записывать данные в канал, пока не заполнятся все области. Задачи могут читать данные из канала, причем данные поступают по принципу FIFO. Попытка записи в переполненный канал или чтения из пустого канала может привести к ошибке или приостановке задачи, в зависимости от выбранных настроек вызовов API и конфигурации Nucleus SE.

Каналы и очереди

Nucleus SE также поддерживает очереди, которые были подробно рассмотрены в предыдущих статьях (#23 и #24). Основное различие между каналами и очередями заключается в размере сообщения. Очереди содержат сообщения, состоящие из одной переменной типа ADDR, обычно это указатели. Канал содержит сообщения произвольного размера, индивидуального для каждого канала в приложении и назначаемого во время настройки параметров.

Настройка каналов

Количество каналов

Как и для большинства объектов Nucleus SE, настройка каналов управляется директивами #define в nuse_config.h. Основным параметром является NUSE_PIPE_NUMBER, который определяет количество сконфигурированных в приложении каналов. По умолчанию это значение равно нулю (т.е. в приложении нет каналов) и может принимать значения вплоть до 16. Некорректное значение приведет к ошибке компиляции, которая будет сгенерирована проверкой в файле nuse_config_check.h (этот файл входит в nuse_config.c и компилируется вместе с ним), что приведет к срабатыванию директивы #error.

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

Активация вызовов API

Каждая функция API (служебный вызов) в Nucleus SE имеет активирующую директиву #define в nuse_config.h. Для каналов такими символами являются:

NUSE_PIPE_SEND
NUSE_PIPE_RECEIVE
NUSE_PIPE_JAM
NUSE_PIPE_RESET
NUSE_PIPE_INFORMATION
NUSE_PIPE_COUNT

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

Ниже приведен фрагмент кода из файла nuse_config.h по умолчанию.

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

Служебные вызовы каналов

Nucleus RTOS поддерживает десять служебных вызовов, связанных с каналами, которые предоставляют следующий функционал:

  • Отправка сообщения в канал. В Nucleus SE реализовано в функции NUSE_Pipe_Send().
  • Прием сообщения из канала. В Nucleus SE реализовано в функции NUSE_Pipe_Receive().
  • Отправка сообщения в начало канала. В Nucleus SE реализовано в NUSE_Pipe_Jam().
  • Восстановление канала в неиспользуемое состояние с освобождением всех приостановленных задач (сброс). В Nucleus SE реализовано в NUSE_Pipe_Reset().
  • Получение информации о конкретном канале. В Nucleus SE реализовано при помощи NUSE_Pipe_Information().
  • Возврат счетчика сконфигурированных на данный момент каналов в приложении. В Nucleus SE реализовано при помощи NUSE_Pipe_Count().
  • Добавление нового канала в приложение (создание). Не реализовано в Nucleus SE.
  • Удаление канала из приложения. Не реализовано в Nucleus SE.
  • Возврат указателей на все каналы в приложении. Не реализовано в Nucleus SE.
  • Отправка сообщения всем задачам, приостановленным на канале (broadcast). Не реализовано в Nucleus SE.
Читайте также:  Статистика способов определения поставщика

Рассмотрим реализацию каждого из этих служебных вызовов подробнее.

Служебные вызовы записи и чтения из каналов

Базовыми операциями, которые выполняются с каналами, являются запись (которую также называют отправкой) и чтение (также известна как прием сообщений). Кроме того, возможна запись данных в начало канала (jamming). Nucleus RTOS и Nucleus SE предоставляют три основных вызова API для этих операций, которые будут рассмотрены ниже.

Запись в канал

Служебный вызов Nucleus RTOS API для записи в канал очень гибкий, что позволяет приостанавливать задачи неявным образом либо с таймаутом, если операцию нельзя завершить немедленно (например, при попытке записи в переполненный канал). Nucleus SE имеет аналогичный вызов, но приостановка задач опциональна, а таймаут не реализован.

Nucleus RTOS также предоставляет службу для трансляции (broadcast) на канал, но в Nucleus SE она не поддерживается. Она будет описана в разделе «Нереализованные вызовы API» в следующей статье.

Вызов для отправки сообщений в канал в Nucleus RTOS

Прототип служебного вызова:

STATUS NU_Send_To_Pipe(NU_PIPE *pipe, VOID *message, UNSIGNED size, UNSIGNED suspend);

pipe – указатель на предоставленный пользователем блок управления каналом;
message – указатель на отправляемое сообщение;
size – количество байт в сообщении. Если канал поддерживает сообщения переменной длины, этот параметр должен быть равен или меньше длины сообщения, поддерживаемой каналом. Если канал поддерживает сообщения фиксированной длины, этот параметр должен быть равен размеру поддерживаемого каналом сообщения;
suspend – спецификация приостановки задачи, может принимать значения NU_NO_SUSPEND, NU_SUSPEND или значение таймаута.

NUSE_SUCCESS – вызов был успешно завершен;
NU_INVALID_PIPE – некорректный указатель на канал;
NU_INVALID_POINTER – нулевой указатель на сообщение (NULL);
NU_INVALID_SIZE – размер сообщения не совместим с размером сообщения, поддерживаемым каналом;
NU_INVALID_SUSPEND – попытка приостановки из не связанного с задачей потока;
NU_PIPE_FULL – канал переполнен, и тип приостановки задачи не был указан;
NU_TIMEOUT – канал переполнен даже после приостановки задачи на указанный период времени;
NU_PIPE_DELETED – канал был удален, пока задача была приостановлена;
NU_PIPE_RESET – канал был сброшен, пока задача была приостановлена.

Вызов для отправки сообщений в канал в Nucleus SE

Этот служебный вызов API поддерживает основной функционал Nucleus RTOS API.

Прототип служебного вызова:

STATUS NUSE_Pipe_Send(NUSE_PIPE pipe, U8 *message, U8 suspend);

pipe – индекс (ID) используемого канала;
message – указатель на отправляемое сообщение (последовательность байтов допустимой для данного канала длины);
suspend – спецификация приостановки задач, может принимать значения NUSE_NO_SUSPEND и NUSE_SUSPEND.

NUSE_SUCCESS – вызов был успешно завершен;
NUSE_INVALID_PIPE – некорректный индекс канала;
NUSE_INVALID_POINTER – нулевой указатель на сообщение (NULL);
NUSE_INVALID_SUSPEND – попытка приостановки из не связанного с задачей потока или при отключенной блокировке задач;
NUSE_PIPE_FULL – канал переполнен, и тип приостановки задач не был указан;
NUSE_PIPE_WAS_RESET – канал был сброшен, пока задача была приостановлена.

Реализация отправки сообщения в канал в Nucleus SE

Вариант кода функции API NUSE_Pipe_Send() (после проверки параметров) выбирается при помощи условной компиляции в зависимости от того, активирована ли поддержка вызовов API для блокировки (приостановки) задач или нет. Ниже мы рассмотрим оба варианта.

Если блокировка отключена, код этого вызова API довольно прост:

Функция проверяет, есть ли свободное пространство в канале, и использует индекс NUSE_Pipe_Head[] для помещения сообщения в область данных канала.

Если блокировка задач активирована, код становится более сложным:

Некоторые пояснения могут быть полезными.

Код заключен в цикл do…while, который выполняется, пока параметр приостановки задач имеет значение NUSE_SUSPEND.

Если канал заполнен и параметр suspend имеет значение NUSE_NO_SUSPEND, вызов API завершается со значением NUSE_PIPE_FULL. Если параметр suspend имеет значение NUSE_SUSPEND, задача приостанавливается. При завершении (то есть когда задача возобновляется), если возвращаемое значение равно NUSE_SUCCESS, то есть задача была возобновлена, потому что сообщение было прочитано (а не потому что канал был сброшен), код возвращается в начало цикла.

Если канал не заполнен, предоставляемое сообщение сохраняется, используя индекс NUSE_Pipe_Head[], в области данных канала. Выполняется проверка, есть ли в канале приостановленные задачи (ожидающие сообщений). Если такие задачи есть, первая из них возобновляется. Переменной suspend присваивается значение NUSE_NO_SUSPEND, а вызов API завершается со значением NUSE_SUCCESS.

Чтение из канала

Служебный вызов Nucleus RTOS API для чтения из канала очень гибкий, что позволяет приостанавливать задачи неявным образом или с таймаутом, если операцию нельзя завершить немедленно (например, при попытке чтения пустого канала). Nucleus SE имеет аналогичный служебный вызов, но приостановка задач опциональна, а таймаут не реализован.

Вызов для чтения из канала в Nucleus RTOS

Прототип служебного вызова:

STATUS NU_Receive_From_Pipe(NU_PIPE *pipe, VOID *message, UNSIGNED size, UNSIGNED *actual_size, UNSIGNED suspend);

pipe – указатель на предоставленный пользователем блок управления каналом;
message – указатель на хранилище для принимаемого сообщения;
size – количество байт в сообщении. Должно совпадать с размером сообщения, указанным при создании канала;
suspend – спецификация приостановки задач, может принимать значения NU_NO_SUSPEND, NU_SUSPEND или значение таймаута.

NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_PIPE – некорректный указатель на канал;
NU_INVALID_POINTER – нулевой указатель на сообщение (NULL);
NU_INVALID_SUSPEND – попытка приостановить задачу из не связанного с задачей потока;
NU_PIPE_EMPTY – канал пуст, и тип приостановки задач не был указан;
NU_TIMEOUT – канал пуст даже после приостановки задачи на указанное значение таймаута;
NU_PIPE_DELETED – канал был удален, пока задача была приостановлена;
NU_PIPE_RESET – канал был удален, пока задача была приостановлена.

Читайте также:  Тест словосочетание со способом связи согласование с ответами

Вызов для чтения из канала в Nucleus SE

Этот служебный вызов API поддерживает основной функционал Nucleus RTOS API.

Прототип служебного вызова:

STATUS NUSE_Pipe_Receive(NUSE_PIPE pipe, U8 *message, U8 suspend);

pipe – индекс (ID) используемого канала;
message – указатель на хранилище для принимаемого сообщение (последовательность байтов, длина которой совпадает с размером сообщения канала);
suspend – спецификация приостановки задач, может принимать значения NUSE_NO_SUSPEND и NU_SUSPEND.

NUSE_SUCCESS – вызов был успешно завершен;
NUSE_INVALID_PIPE – некорректный индекс канала;
NUSE_INVALID_POINTER – нулевой указатель на сообщение (NULL);
NUSE_INVALID_SUSPEND – попытка приостановки задачи из не связанного с задачей потока или при отключенной поддержке приостановки задач;
NUSE_PIPE_EMPTY – канал пуст, и тип приостановки задач не был указан;
NUSE_PIPE_WAS_RESET – канал был сброшен, пока задача была приостановлена.

Реализация чтения каналов в Nucleus SE

Вариант кода функции API NUSE_Pipe_Receive() (после проверки параметров) выбирается условной компиляцией в зависимости от того, активирована ли поддержка API вызовов блокировки (приостановки задач) или нет. Мы рассмотрим оба варианта ниже.

Если блокировка отключена, код этого вызова API довольно прост:

Функция проверяет наличие сообщения в канале и использует индекс NUSE_Pipe_Tail[], чтобы получить сообщение из области данных канала, и возвращает данные через указатель сообщения.

Если блокировка задач активирована, код становится более сложным:

Некоторые пояснения могут быть полезны.

Код заключен в цикл do…while, который выполняется, пока параметр приостановки задач имеет значение NUSE_SUSPEND.

Если канал пуст и параметр suspend имеет значение NUSE_NO_SUSPEND, вызов API завершается со значением NUSE_PIPE_EMPTY. Если параметр suspend имеет значение NUSE_SUSPEND, задача приостанавливается. При завершении (то есть когда задача возобновляется), если возвращаемое значение равно NUSE_SUCCESS, то есть задача была возобновлена, потому что сообщение было отправлено (а не потому что канал был сброшен), код возвращается в начало цикла.

Если канал содержит в себе сообщения, хранимое сообщение возвращается, используя индекс NUSE_Pipe_Tail[]. Выполняется проверка, есть ли приостановленные (ожидающие отправки) задачи на этом канале. Если такие задачи есть, первая из них возобновляется. Переменной suspend присваивается значение NUSE_NO_SUSPEND, и вызов API завершается с кодом NUSE_SUCCESS.

Запись в начало канала

Служебный вызов Nucleus RTOS API для записи в начало канала очень гибкий, что позволяет приостанавливать задачи неявным образом или с таймаутом, если операцию нельзя завершить немедленно (например, при попытке записи в полный канал). Nucleus SE имеет аналогичный служебный вызов, но приостановка задач опциональна, а таймаут не реализован.

Вызов для записи в начало канала в Nucleus RTOS

Прототип служебного вызова:

STATUS NU_Send_To_Front_Of_Pipe(NU_PIPE *pipe, VOID *message, UNSIGNED size, UNSIGINED suspend);

pipe – указатель на предоставленный пользователем блок управления каналом;
message – указатель на отправляемое сообщение;
size – количество байт в сообщении. Если канал поддерживает сообщения переменной длины, этот параметр должен быть равен или меньше размера сообщения, поддерживаемого каналом. Если канал поддерживает сообщения фиксированной длины, этот параметр должен совпадать с размером сообщения, поддерживаемым каналом;
suspend – спецификация приостановки задач, может принимать значения NU_NO_SUSPEND, NU_SUSPEND или значение таймаута.

NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_PIPE – некорректный указатель на канал;
NU_INVALID_POINTER – нулевой указатель на сообщение (NULL);
NU_INVALID_SIZE – размер сообщения несовместим с размером сообщения, поддерживаемым каналом;
NU_INVALID_SUSPEND – попытка приостановки из несвязанного с задачей потока;
NU_PIPE_FULL – канал полон, и тип приостановки задач не был указан;
NU_TIMEOUT – канал полон даже после приостановки задачи на указанный период времени;
NU_PIPE_DELETED – канал был удален, пока задача была приостановлена;
NU_PIPE_RESET – канал был сброшен, пока задача была приостановлена.

Вызов для записи в начало канала в Nucleus SE

Этот служебный вызов поддерживает основной функционал Nucleus RTOS API.

Прототип служебного вызова:

STATUS NUSE_Pipe_Jam(NUSE_PIPE pipe, ADDR *message, U8 suspend);

pipe – индекс (ID) используемого канала;
message – указатель на отправляемое сообщение, которое является последовательностью байтов, равную сконфигурированному размеру сообщения в канале;
suspend – спецификация приостановки задач, может принимать значения NUSE_NO_SUSPEND или NUSE_SUSPEND.

NUSE_SUCCESS – вызов был успешно завершен;
NUSE_INVALID_PIPE – некорректный индекс канала;
NUSE_INVALID_POINTER – нулевой указатель на сообщение (NULL);
NUSE_INVALID_SUSPEND – попытка приостановки задачи из не связанного с задачей потока или при отключенной блокировке задач;
NUSE_PIPE_FULL – канал полон, и тип приостановки задач не был указан;
NUSE_PIPE_WAS_RESET – канал был сброшен, пока задача была приостановлена.

Реализация записи в начало канала в Nucleus SE

Вариант кода функции NUSE_Pipe_Jam() очень похож на NUSE_Pipe_Send(), кроме того, что для хранения в нем данных используется индекс NUSE_Pipe_Tail[], следовательно:

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

Источник

Оцените статью
Разные способы