You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
282 lines
16 KiB
282 lines
16 KiB
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 байта на пакет + опциональные метаданные |