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; код приложения на стороне камеры, который выбрал собственную обработку ошибок, видит их как возвращаемые значения из функций обратного вызова бэкенда. Большинству приложений камеры вообще не нужно смотреть на коды состояния – библиотека сама обрабатывает повторные попытки, и до приложения доходят только действительно невосстановимые сбои (например, исчезновение самого транспорта).
Когда есть кадрирование для обнаружения повреждений и надёжность для восстановления после них, работа на уровне канала связи завершена. Код приложения видит кадрированные, упорядоченные, целостные пакеты; байты внутри них вольны означать всё, что захочет вышестоящий канал.