12.5. Надёжность – последовательности, ACK, повторные передачи#

Уровень кадрирования обнаруживает повреждения с помощью своих CRC. Уровень надёжности превращает «обнаруженное повреждение» в «приложение никогда не видит испорченные данные», договариваясь о повторной передаче всякий раз, когда пакет не приходит в целости.

12.5.1. Номера последовательности#

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

Вместо чистого пакета в правильном порядке у получателя может появиться три варианта:

  • Ожидаемый номер последовательности с корректным CRC. Пакет передаётся на следующий уровень.

  • Ожидаемый номер последовательности с неверным CRC. Получатель отбрасывает пакет и (если согласованы ACK) отправляет NAK с запросом повторной передачи.

  • Номер последовательности на единицу больше ожидаемого, с корректным CRC. Получатель понимает, что предыдущий пакет потерялся; он отправляет NAK со ссылкой на пропущенный номер и откладывает новый пакет.

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

12.5.2. ACK и NAK#

Два бита флагов в заголовке пакета несут сам трафик надёжности:

  • Флаг ACK_REQ, установленный в исходящем пакете, означает «я хочу получить подтверждение в ответ». Пакеты данных обычно его устанавливают; пинги состояния и одноразовые события могут этого не делать.

  • Флаг ACK, установленный в пакете, означает «этот пакет является подтверждением для номера последовательности в заголовке». Сам он не несёт никакой полезной нагрузки.

  • Флаг NAK означает «этот пакет отклоняет предыдущий» – обычно из-за неверного CRC или пропуска в номерах последовательности. Заголовок указывает отправителю, какой номер последовательности нужно передать повторно.

Отправитель работает по схеме «остановка и ожидание»: он передаёт один пакет, требующий подтверждения, затем ждёт соответствующего ACK (или NAK), прежде чем отправить следующий. Модель с одним пакетом «в полёте» удерживает состояние отправителя ограниченным – несколько сотен байт на самых маленьких камерах – и соответствует роли протокола как канала управления между двумя конечными точками, а не оптимизированного по пропускной способности конвейера. При получении NAK отправитель передаёт тот же пакет повторно с установленным флагом RTX, чтобы получатель знал, что это повтор.

12.5.3. Тайминг повторной передачи#

Если в течение таймаута повторной передачи не приходит ни ACK, ни NAK, отправитель сам передаёт пакет «в полёте» повторно. Таймаут по умолчанию равен 500 ms и удваивается с каждой последующей попыткой (1 с, 2 с, …). После заданного числа попыток – по умолчанию трёх – отправитель сдаётся и сообщает приложению об ошибке транспорта.

Удвоение таймаута – это стандартный приём экспоненциальной задержки (exponential backoff). Короткий первый таймаут быстро отлавливает потерянные пакеты; удвоение означает, что хост, занятый на несколько сотен миллисекунд, не вызывает шквал дубликатов, которые усугубляют нагрузку.

12.5.4. Настройка надёжности#

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

  • protocol.init(ack=False) отключает попакетные ACK. Отправитель работает по принципу «отправил и забыл»; получатель доставляет всё, что приходит. Подходит для потоковой передачи данных датчика, где устаревший образец допустим.

  • protocol.init(seq=False) отключает отслеживание номеров последовательности, что подразумевает и отключение ACK. Полезно только на абсолютно надёжных транспортах.

  • protocol.init(crc=False) отключает проверку CRC, но оставляет остальную часть кадрирования нетронутой. Стоит делать только тогда, когда сам транспорт достаточно надёжен, чтобы ошибки CRC не возникали.

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

12.5.5. Коды состояния#

Когда ошибка транспорта всё же доходит до кода приложения, она приходит в виде кода состояния. Библиотека протокола определяет десять из них:

  • SUCCESS – операция завершена.

  • FAILED – команда завершилась неудачей по неуказанной причине.

  • INVALID – получатель отклонил команду или один из её аргументов.

  • TIMEOUT – истёк таймер повторных попыток.

  • BUSY – камера занята (как правило, заблокированный канал).

  • CHECKSUM – CRC заголовка или полезной нагрузки не совпал.

  • SEQUENCE – номер последовательности оказался вне порядка настолько, что уровень не смог это восстановить.

  • OVERFLOW – полезная нагрузка превысила согласованный максимум.

  • FRAGMENT – многофрагментное сообщение пришло с недостающими частями.

  • UNKNOWN – защитный универсальный код для действительно неожиданных ситуаций.

Код хоста, вызывающий channel_read(), видит их как исключения Python; код приложения на стороне камеры, который выбрал собственную обработку ошибок, видит их как возвращаемые значения из функций обратного вызова бэкенда. Большинству приложений камеры вообще не нужно смотреть на коды состояния – библиотека сама обрабатывает повторные попытки, и до приложения доходят только действительно невосстановимые сбои (например, исчезновение самого транспорта).

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