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

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 байта на пакет + опциональные метаданные