ETCP - Extended Transmission Control Protocol ============================================= Протокол для надежной передачи данных поверх UDP с восстановлением порядка, повторной передачей потерянных пакетов и управлением потоком. Основные задачи: - Передача пакетов через UDP с учетом его особенностей (потери, негарантированный порядок) - Восстановление правильного порядка пакетов на приемной стороне - Повторная передача потерянных пакетов - Управление потоком на основе измерения RTT и пропускной способности - Обеспечение прогресса доставки даже при длительных потерях Архитектура: ------------ На передающей стороне: 1. Очередь передачи (tx_queue) - данные, ожидающие отправки 2. Список отправленных пакетов (sent_list) - пакеты, ожидающие подтверждения 3. Таймеры для управления передачей и повторной отправкой На приемной стороне: 1. Сортированный список принятых пакетов (rx_list) - пакеты в порядке ID 2. Выходная очередь (output_queue) - собранные в правильном порядке данные 3. Буферы для накопления подтверждений и запросов на повторную передачу Структура данных: ----------------- struct epkt { // Очереди ll_queue_t* tx_queue; // Очередь данных для отправки ll_queue_t* output_queue; // Выходная очередь (собранные данные) // Списки пакетов struct rx_packet* rx_list; // Полученные пакеты (отсортированный список) struct sent_packet* sent_list; // Отправленные пакеты (для повторной передачи) // Метрики uint16_t rtt_last; // Последнее RTT (в единицах времени 0.1 мс) uint16_t rtt_avg_10; // Среднее RTT за последние 10 пакетов uint16_t rtt_avg_100; // Среднее RTT за последние 100 пакетов uint16_t jitter; // Джиттер (усредненный) uint16_t bandwidth; // Текущая пропускная способность (байты за единицу времени) uint32_t bytes_sent_total; // Общее количество отправленных байт uint16_t last_sent_timestamp; // Временная метка последнего отправленного пакета uint32_t bytes_allowed; // Рассчитанное количество разрешенных к отправке байт // Состояние передачи uint16_t next_tx_id; // Следующий ID для передачи uint16_t last_sent_id; // Последний отправленный ID (для ретрансмиссии самого нового пакета) uint16_t last_rx_id; // Последний полученный ID (для подтверждения) uint16_t last_delivered_id; // Последний ID, переданный в output_queue // Таймеры void* next_tx_timer; // Таймер для следующей передачи void* retransmit_timer; // Таймер для повторных передач // Обратный вызов для отправки etcp_tx_callback_t tx_callback; void* tx_callback_arg; // История RTT для усреднения uint16_t rtt_history[100]; uint8_t rtt_history_idx; uint8_t rtt_history_count; // Ожидающие подтверждения uint16_t pending_ack_ids[32]; uint16_t pending_ack_timestamps[32]; uint8_t pending_ack_count; // Ожидающие запросы на повторную передачу uint16_t pending_retransmit_ids[32]; uint8_t pending_retransmit_count; // Управление окном uint32_t unacked_bytes; // Количество байт, отправленных но еще не подтвержденных uint32_t window_size; // Текущий размер окна в байтах (рассчитывается) uint16_t last_acked_id; // Последний подтвержденный ID пакета uint16_t last_rx_ack_id; // Последний полученный ID подтверждения от получателя uint16_t retrans_timer_period; // Текущий период таймера повторной передачи (в единицах времени) uint16_t next_retrans_time; // Время следующей проверки повторной передачи uint8_t window_blocked; // Флаг: передача заблокирована из-за ограничения окна // Отслеживание прогресса доставки uint16_t oldest_missing_id; // Самый старый отсутствующий ID пакета uint16_t missing_since_time; // Время, когда самый старый отсутствующий пакет был впервые обнаружен }; Формат пакета: --------------- Пакет состоит из обязательного заголовка и опциональных секций: 1. Обязательный заголовок (4 байта): - ID пакета (2 байта): циклический порядковый номер (0 для пакетов только с метриками) - Timestamp (2 байта): время отправки в единицах 0.1 мс (циклическое, 16 бит) 2. Опциональные секции (одна или несколько, каждая начинается с байта-заголовка): а) Подтверждения (ACK) - заголовок 0x01: [0x01] [count] [(id, timestamp) × count] [last_delivered_id] [last_rx_id] - count: количество пар ID+timestamp (1 байт) - Для каждого подтверждаемого пакета: ID (2 байта) + timestamp получения (2 байта) - last_delivered_id (2 байта): последний ID, доставленный в выходную очередь - last_rx_id (2 байта): последний полученный ID (новейший известный пакет) б) Запросы на повторную передачу - заголовок 0x10-0x2F: [0x10 + (count-1)] [IDs × count] [last_delivered_id] [last_rx_id] - count: (заголовок & 0x0F) + 1 (от 1 до 32) - Для каждого запрашиваемого пакета: ID (2 байта) - last_delivered_id (2 байта): последний ID, доставленный в выходную очередь - last_rx_id (2 байта): последний полученный ID в) Полезные данные - заголовок 0x00: [0x00] [данные...] - Данные произвольной длины (до конца пакета) Пакет может содержать несколько секций (например, ACK + данные). Секция с полезными данными обычно идет последней, если присутствует. Алгоритмы: ---------- 1. Управление передачей: - Передача происходит при наличии данных в tx_queue и доступной полосы пропускания - Полоса пропускания контролируется через bytes_allowed, который накапливается со временем - Размер окна рассчитывается как: window_size = RTT × bandwidth × 2 - Передача блокируется, если unacked_bytes превышает window_size 2. Подтверждение и RTT измерение: - При получении пакета его ID и timestamp добавляются в pending_ack_ids - При следующей отправке эти подтверждения включаются в пакет - Получатель вычисляет RTT как разницу между текущим временем и полученным timestamp - RTT усредняется за последние 10 и 100 пакетов 3. Повторная передача: - Отправленные пакеты хранятся в sent_list до подтверждения - Таймер retransmit_timer периодически проверяет пакеты старше 1.5×RTT - Если пакет не подтвержден, его ID добавляется в pending_retransmit_ids - Новейший неподтвержденный пакет (last_sent_id) повторно передается после 2×RTT - Запросы на повторную передачу от получателя также обрабатываются 4. Сборка пакетов на приемной стороне: - Полученные пакеты вставляются в отсортированный rx_list - При обнаружении пропусков (gaps) отправляются запросы на повторную передачу - Непрерывная последовательность пакетов перемещается в output_queue - last_delivered_id отслеживает последний доставленный ID 5. Обеспечение прогресса доставки (forward progress): - Отслеживается самый старый отсутствующий пакет (oldest_missing_id) - Если пакет отсутствует дольше 3×RTT (минимум 6 мс), last_delivered_id продвигается вперед - Это предотвращает бесконечное ожидание потерянных пакетов 6. Синхронизация состояния: - Поля last_delivered_id и last_rx_id передаются в ACK и запросах на повторную передачу - Получатель обновляет свой last_delivered_id, если полученное значение новее - Это позволяет синхронизировать прогресс доставки между отправителем и получателем Таймеры: -------- 1. Таймер передачи (next_tx_timer): - Срабатывает, когда передача невозможна (нет полосы или окно заполнено) - Перезапускает процесс передачи 2. Таймер повторной передачи (retransmit_timer): - Период: max(RTT/2, 2 мс) - Проверяет sent_list на наличие неподтвержденных пакетов старше 1.5×RTT - Планирует повторную передачу API функции: ------------ epkt_t* etcp_init(void); Инициализирует новый экземпляр ETCP void etcp_free(epkt_t* epkt); Освобождает экземпляр ETCP и все связанные ресурсы void etcp_set_callback(epkt_t* epkt, etcp_tx_callback_t cb, void* arg); Устанавливает обратный вызов для отправки пакетов через UDP int etcp_rx_input(epkt_t* epkt, uint8_t* pkt, uint16_t len); Обрабатывает полученный UDP пакет int etcp_tx_put(epkt_t* epkt, uint8_t* data, uint16_t len); Помещает данные в очередь передачи ll_queue_t* etcp_get_output_queue(epkt_t* epkt); Возвращает выходную очередь для чтения полученных данных void etcp_set_bandwidth(epkt_t* epkt, uint16_t bandwidth); Устанавливает ограничение пропускной способности int etcp_tx_queue_size(epkt_t* epkt); Возвращает общее количество пакетов, ожидающих в очередях передачи void etcp_reset(epkt_t* epkt); Сбрасывает состояние соединения (очищает очереди, метрики, таймеры) uint16_t etcp_get_rtt(epkt_t* epkt); Возвращает текущее RTT uint16_t etcp_get_jitter(epkt_t* epkt); Возвращает текущий джиттер Внутренние структуры: --------------------- typedef struct rx_packet { struct rx_packet* next; uint16_t id; uint16_t timestamp; uint8_t* data; uint16_t data_len; uint8_t has_payload; } rx_packet_t; typedef struct sent_packet { struct sent_packet* next; uint16_t id; uint16_t timestamp; uint8_t* data; uint16_t data_len; // Общая длина пакета uint16_t payload_len; // Длина полезных данных (для учета окна) uint16_t send_time; // Время отправки uint8_t need_ack; // Требуется подтверждение uint8_t need_retransmit; // Требуется повторная передача } sent_packet_t; Особенности реализации: ----------------------- 1. Циклические счетчики: - ID пакетов: 16-битные, циклические (0-65535, затем 0) - Timestamp: 16-битные, циклические (0-65535 единиц по 0.1 мс ≈ 6.55 секунд) - Сравнение с учетом цикличности через функцию id_compare() 2. Единицы времени: - Базовый интервал: 0.1 мс (100 микросекунд) - Все таймеры и измерения RTT используют эту единицу 3. Ограничения: - Максимум 32 ожидающих подтверждения или запроса на повторную передачу - История RTT хранит до 100 измерений - Максимальный размер окна: 2^32-1 байт 4. Обработка дубликатов: - При получении пакета с уже существующим ID он игнорируется - Повторная передача пакета имеет тот же ID, но новый timestamp Пример использования: --------------------- 1. Инициализация: epkt_t* epkt = etcp_init(); etcp_set_callback(epkt, udp_send_callback, udp_socket); 2. Отправка данных: etcp_tx_put(epkt, data, len); 3. Обработка входящих пакетов: etcp_rx_input(epkt, pkt, len); 4. Чтение полученных данных: ll_queue_t* output = etcp_get_output_queue(epkt); ll_entry_t* entry = queue_entry_get(output); // Обработка entry... 5. Освобождение: etcp_free(epkt); Примечания: ----------- - Протокол предназначен для работы в условиях умеренных потерь и реордеринга - Механизм forward progress обеспечивает доставку даже при потере начальных пакетов - Управление окном предотвращает перегрузку сети - Поддержка двунаправленной связи (каждая сторона может быть одновременно отправителем и получателем) - Минимальные накладные расходы: 4 байта на пакет + опциональные метаданные