В некоторых приложениях простое использование TCP не может удовлетворить потребности. Прямое использование дейтаграмм UDP не может гарантировать надежность данных, и часто необходимо реализовать надежный протокол передачи на основе UDP на уровне приложений.
Непосредственное использование протокола KCP является опцией, которая реализует надежный протокол автоматической повторной передачи и обеспечивает бесплатную настройку параметров поверх него. Адаптируйтесь к потребностям различных сценариев с помощью параметров конфигурации и соответствующих методов вызова.
Введение в ККП:
KCP — это быстрый и надежный протокол, который может сократить среднюю задержку на 30–40 % и уменьшить максимальную задержку в три раза за счет увеличения пропускной способности на 10–20 % по сравнению с TCP. Реализация чистого алгоритма не отвечает за отправку и получение базовых протоколов (таких как UDP). Пользователям необходимо определить метод отправки пакетов данных нижнего уровня и предоставить его KCP в форме обратного вызова. Даже часы необходимо передать извне, и никаких внутренних системных вызовов не будет. Весь протокол имеет только два исходных файла: ikcp.h и ikcp.c, которые можно легко интегрировать в собственный стек протоколов пользователя. Возможно, вы реализовали протокол на основе P2P или UDP, но у вас нет полной и надежной реализации протокола ARQ. Тогда просто скопируйте эти два файла в существующий проект, напишите несколько строк кода, и вы сможете его использовать.
В этой статье кратко описываются базовый процесс отправки и получения, окно перегрузки и алгоритм тайм-аута протокола KCP, а также приводится справочный пример кода.
Указанная версия KCP является последней на момент написания. В этой статье не будет полностью вставлен весь исходный код KCP, но будут добавлены ссылки на соответствующие места исходного кода в ключевых точках.
Структура IKCPSEG используется для хранения состояния отправленных и полученных сегментов данных.
Описание всех полей IKCPSEG:
struct IKCPSEG
{
/* 队列节点,IKCPSEG 作为一个队列元素,此结构指向了队列后前后元素 */
struct IQUEUEHEAD node;
/* 会话编号 */
IUINT32 conv;
/* 指令类型 */
IUINT32 cmd;
/* 分片号 (fragment)
发送数据大于 MSS 时将被分片,0为最后一个分片.
意味着数据可以被recv,如果是流模式,所有分片号都为0
*/
IUINT32 frg;
/* 窗口大小 */
IUINT32 wnd;
/* 时间戳 */
IUINT32 ts;
/* 序号 (sequence number) */
IUINT32 sn;
/* 未确认的序号 (unacknowledged) */
IUINT32 una;
/* 数据长度 */
IUINT32 len;
/* 重传时间 (resend timestamp) */
IUINT32 resendts;
/* 重传的超时时间 (retransmission timeout) */
IUINT32 rto;
/* 快速确认计数 (fast acknowledge) */
IUINT32 fastack;
/* 发送次数 (transmit) */
IUINT32 xmit;
/* 数据内容 */
char data[1];
};
Поле data
в конце структуры используется для индексации данных в конце структуры. Дополнительная выделенная память увеличивает фактическую длину массива полей данных во время выполнения (ikcp.c:173).
Структура IKCPSEG представляет собой только состояние памяти, и только некоторые поля кодируются в транспортном протоколе.
Функция ikcp_encode_seg кодирует заголовок транспортного протокола:
/* 协议头一共 24 字节 */
static char *ikcp_encode_seg(char *ptr, const IKCPSEG *seg)
{
/* 会话编号 (4 Bytes) */
ptr = ikcp_encode32u(ptr, seg->conv);
/* 指令类型 (1 Bytes) */
ptr = ikcp_encode8u(ptr, (IUINT8)seg->cmd);
/* 分片号 (1 Bytes) */
ptr = ikcp_encode8u(ptr, (IUINT8)seg->frg);
/* 窗口大小 (2 Bytes) */
ptr = ikcp_encode16u(ptr, (IUINT16)seg->wnd);
/* 时间戳 (4 Bytes) */
ptr = ikcp_encode32u(ptr, seg->ts);
/* 序号 (4 Bytes) */
ptr = ikcp_encode32u(ptr, seg->sn);
/* 未确认的序号 (4 Bytes) */
ptr = ikcp_encode32u(ptr, seg->una);
/* 数据长度 (4 Bytes) */
ptr = ikcp_encode32u(ptr, seg->len);
return ptr;
}
Структура IKCPCB хранит весь контекст протокола KCP, а связь по протоколу осуществляется путем создания двух объектов IKCPCB на противоположном конце.
struct IKCPCB
{
/* conv: 会话编号
mtu: 最大传输单元
mss: 最大报文长度
state: 此会话是否有效 (0: 有效 ~0:无效)
*/
IUINT32 conv, mtu, mss, state;
/* snd_una: 发送的未确认数据段序号
snd_nxt: 发送的下一个数据段序号
rcv_nxt: 期望接收到的下一个数据段的序号
*/
IUINT32 snd_una, snd_nxt, rcv_nxt;
/* ts_recent: (弃用字段?)
ts_lastack: (弃用字段?)
ssthresh: 慢启动阈值 (slow start threshold)
*/
IUINT32 ts_recent, ts_lastack, ssthresh;
/* rx_rttval: 平滑网络抖动时间
rx_srtt: 平滑往返时间
rx_rto: 重传超时时间
rx_minrto: 最小重传超时时间
*/
IINT32 rx_rttval, rx_srtt, rx_rto, rx_minrto;
/* snd_wnd: 发送窗口大小
rcv_wnd: 接收窗口大小
rmt_wnd: 远端窗口大小
cwnd: 拥塞窗口 (congestion window)
probe: 窗口探测标记位,在 flush 时发送特殊的探测包 (window probe)
*/
IUINT32 snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe;
/* current: 当前时间 (ms)
interval: 内部时钟更新周期
ts_flush: 期望的下一次 update/flush 时间
xmit: 全局重传次数计数
*/
IUINT32 current, interval, ts_flush, xmit;
/* nrcv_buf: rcv_buf 接收缓冲区长度
nsnd_buf: snd_buf 发送缓冲区长度
nrcv_que: rcv_queue 接收队列长度
nsnd_que: snd_queue 发送队列长度
*/
IUINT32 nrcv_buf, nsnd_buf;
IUINT32 nrcv_que, nsnd_que;
/* nodelay: nodelay模式 (0:关闭 1:开启)
updated: 是否调用过 update 函数
*/
IUINT32 nodelay, updated;
/* ts_probe: 窗口探测标记位
probe_wait: 零窗口探测等待时间,默认 7000 (7秒)
*/
IUINT32 ts_probe, probe_wait;
/* dead_link: 死链接条件,默认为 20。
(单个数据段重传次数到达此值时 kcp->state 会被设置为 UINT_MAX)
incr: 拥塞窗口算法的一部分
*/
IUINT32 dead_link, incr;
/* 发送队列 */
struct IQUEUEHEAD snd_queue;
/* 接收队列 */
struct IQUEUEHEAD rcv_queue;
/* 发送缓冲区 */
struct IQUEUEHEAD snd_buf;
/* 接收缓冲区 */
struct IQUEUEHEAD rcv_buf;
/* 确认列表, 包含了序号和时间戳对(pair)的数组元素*/
IUINT32 *acklist;
/* 确认列表元素数量 */
IUINT32 ackcount;
/* 确认列表实际分配长度 */
IUINT32 ackblock;
/* 用户数据指针,传入到回调函数中 */
void *user;
/* 临时缓冲区 */
char *buffer;
/* 是否启用快速重传,0:不开启,1:开启 */
int fastresend;
/* 快速重传最大次数限制,默认为 5*/
int fastlimit;
/* nocwnd: 控流模式,0关闭,1不关闭
stream: 流模式, 0包模式 1流模式
*/
int nocwnd, stream;
/* 日志标记 */
int logmask;
/* 发送回调 */
int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user);
/* 日志回调 */
void (*writelog)(const char *log, struct IKCPCB *kcp, void *user);
};
typedef struct IKCPCB ikcpcb;
В KCP всего 2 структуры очередей:
IQUEUEHEAD — это простой двусвязный список, указывающий на начальный (предыдущий) и последний (следующий) элементы очереди:
struct IQUEUEHEAD {
/*
next:
作为队列时: 队列的首元素 (head)
作为元素时: 当前元素所在队列的下一个节点
prev:
作为队列时: 队列的末元素 (last)
作为元素时: 当前元素所在队列的前一个节点
*/
struct IQUEUEHEAD *next, *prev;
};
typedef struct IQUEUEHEAD iqueue_head;
Когда очередь пуста, next/prev будет указывать на саму очередь, а не на NULL.
Заголовок структуры IKCPSEG как элемент очереди также повторно использует структуру IQUEUEHEAD:
struct IKCPSEG
{
struct IQUEUEHEAD node;
/* ... */
}
При использовании в качестве элемента очереди записываются предыдущий (prev) элемент и следующий элемент (next) в очереди, где находится текущий элемент.
Когда prev указывает на очередь, это означает, что текущий элемент находится в начале очереди, а когда next указывает на очередь, это означает, что текущий элемент находится в конце очереди.
Все операции с очередью предоставляются в виде макросов.
Методы настройки, предоставляемые KCP:
Варианты режима работы :
int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc)
Максимальные параметры окна :
int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
Размер окна отправки sndwnd
должен быть больше 0, а размер окна приема rcvwnd
должен быть больше 128. Единицей измерения являются пакеты, а не байты.
Максимальная единица передачи :
KCP не несет ответственности за определение MTU. Значение по умолчанию — 1400 байт. Для установки этого значения можно использовать ikcp_setmtu. Это значение повлияет на максимальную единицу передачи при объединении и фрагментации пакетов данных. Меньший MTU повлияет на приоритет маршрутизации.
В этой статье представлен код kcp_basic.c, который может запускать KCP. Пример кода длиной менее 100 строк представляет собой чистый вызов алгоритма KCP и не включает в себя какое-либо сетевое планирование. ( Важно : следуйте статье, чтобы отладить ее и попробовать!)
Вы можете использовать его, чтобы получить предварительное представление об основных полях структуры в структуре IKCPCB:
kcp.snd_queue
: очередь отправки (длина записи kcp.nsnd_que
)kcp.snd_buf
: буфер отправки (длина записи kcp.nsnd_buf
)kcp.rcv_queue
: очередь приема (длина записи kcp.nrcv_que
)kcp.rcv_buf
: буфер приема (длина записи kcp.nrcv_buf
)kcp.mtu
: максимальная единица передачиkcp.mss
: максимальная длина сообщения.А в структуре IKCPSEG:
seg.sn
: серийный номерseg.frg
: Номер сегментаСоздайте структуру контекста KCP IKCPCB с помощью функции ikcp_create.
IKCPCB внутренне создает соответствующую структуру IKCPSEG для хранения данных и статуса путем внешнего вызова ikcp_send (ввод данных пользователя отправителю) и ikcp_input входных данных (ввод отправителя получателю).
Кроме того, структура IKCPSEG будет удалена посредством ikcp_recv (удаляется пользователем с принимающей стороны) и данных подтверждения ikcp_input (полученных отправляющей стороной от принимающей стороны).
Подробную информацию о направлении потока данных см. в разделе «Очередь и окно» .
Создание и уничтожение IKCPSEG в основном происходит в четырех вышеупомянутых ситуациях, а другие часто встречаются при перемещениях между внутренними очередями и других оптимизациях.
В следующих статьях все появившиеся поля структуры IKCPCB и IKCPSEG будут выделены
标记
(только просмотр с уценкой, другие могут этого не увидеть). Все поля структуры IKCPCB будут иметь префиксkcp.
, а все поля структуры IKCPSEG будут иметь префиксseg.
. Обычно соответствующее имя переменной или имя параметра функции в исходном коде такжеkcp
илиseg
.
Этот код просто записывает данные указанной длины в объект KCP с именем k1 и считывает данные из объекта k2. KCP настроен в режиме по умолчанию.
Основной процесс можно просто описать с помощью псевдокода следующим образом:
/* 创建两个 KCP 对象 */
k1 = ikcp_create()
k2 = ikcp_create()
/* 向发送端 k1 写入数据 */
ikcp_send(k1, send_data)
/* 刷出数据,执行 kcp->output 回调 */
ikcp_flush(k1)
/* output 回调接收到带协议包头的分片数据,执行发送 */
k1->output(fragment_data)
/* 接收端 k2 收到输入数据 */
ikcp_input(k2, input_data)
/* 接收端刷出数据,会发送确认包到 k1 */
ikcp_flush(k2)
/* 发送端 k1 收到确认数据 */
recv_data = ikcp_recv(k1, ack_data)
/* 尝试读出数据 */
recv = ikcp_recv(k2)
/* 验证接收数据和发送数据一致 */
assert(recv_data == send_data)
В примере кода созданный объект KCP привязан к функции kcp_user_output в дополнение к kcp.output
которая используется для определения поведения выходных данных объекта KCP. kcp.writelog
также привязан к функции kcp_user_writelog для отладки печати.
Кроме того, поскольку обратный вызов kcp.output
не может рекурсивно вызывать другой ikcp_input (поскольку он в конечном итоге будет рекурсивно обращаться к своему собственному kcp.output
), все выходные данные должны храниться в промежуточном месте, а затем вводиться в k2 после выхода из kcp.output
функция. Это цель структуры OUTPUT_CONTEXT, определенной в примере кода.
Попробуйте запустить пример кода, и вы получите следующий результат (содержимое, добавленное со знаком #, является пояснением):
# k1.output 被调用,输出 1400 字节
k1 [RO] 1400 bytes
# k2 被调用 ikcp_input 输入数据
k2 [RI] 1400 bytes
# psh 数据推送分支处理
k2 input psh: sn=0 ts=0
# k2.output 被调用,输出确认包,数据长度24字节
k2 [RO] 24 bytes
# k1 被调用 ikcp_input 输入数据
k1 [RI] 24 bytes
# 序号 sn=0 被确认
k1 input ack: sn=0 rtt=0 rto=100
k1 [RO] 1400 bytes
k1 [RO] 1368 bytes
k2 [RI] 1400 bytes
k2 input psh: sn=1 ts=0
k2 [RI] 1368 bytes
k2 input psh: sn=2 ts=0
k2 [RO] 48 bytes
k1 [RI] 48 bytes
k1 input ack: sn=1 rtt=0 rto=100
k1 input ack: sn=2 rtt=0 rto=100
# k2 被调用 kcp_recv 取出数据
k2 recv sn=0
k2 recv sn=1
k2 recv sn=2
Выходное содержимое — это отладочная информация, встроенная в код KCP. Префикс строки k1/k2 дополнительно добавляется через kcp_user_writelog в качестве различия.
Полная схема процесса подтверждения отправки этого кода описывается как (в два раза больше):
Вызовите ikcp_send на k1: (рис. шаг 1-1)
Отправителю записываются данные длиной 4096. Согласно kcp.mss
он разрезан на три пакета длиной 1376/1376/1344, а метки фрагментации seg.frg
каждого пакета равны 2/1/0 соответственно.
Максимальная единица передачи kcp.mtu
определяет максимальную длину данных, каждый раз получаемых обратным вызовом ikcp.output
. Значение по умолчанию — 1400.
На схематическом представлении метод ikcp_output в конечном итоге вызывает указатель функции
ikcp.output
. (ikcp.c:212)
Максимальная длина сообщения kcp.mss
рассчитывается путем вычитания служебных данных протокола (24 байта) из kcp.mtu
. Значение по умолчанию — 1376.
В это время обратный вызов kcp.output
выполняться не будет, а все данные сегментов будут выделены, записаны в структуру IKCPSEG и добавлены в очередь kcp.snd_queue
(ikcp.c:528).
В это время длина очереди kcp.snd_queue
k1 равна 3, а длина очереди kcp.snd_buf
равна 0.
Вызовите ikcp_flush на k1: (рис. шаг 1-2)
Здесь игнорируется конкретный процесс расчета окна. Вам нужно только знать, что значение окна перегрузки
kcp.cwnd
равно 1, когда k1 вызывает ikcp_flush в первый раз.
Из-за ограничения окна перегрузки в первый раз можно отправить только один пакет. Объект IKCPSEG с первой длиной данных очереди kcp.snd_queue
перемещается в очередь kcp.snd_buf
(ikcp.c:1028), а значение порядкового номера seg.sn
назначенного в соответствии с kcp.snd_nxt
равно 0 (ikcp .c:1036) поле seg.cmd
— IKCP_CMD_PUSH, Представляет пакет принудительной отправки данных.
В это время длина очереди kcp.snd_queue
k1 равна 2, а длина очереди kcp.snd_buf
равна 1.
На шагах 1–3 выполните вызов ikcp_output (ikcp.c:1113) для первых отправленных данных, чтобы отправить пакет данных [PSH sn=0 frg=2 len=1376]
.
Существует только четыре типа команд данных: IKCP_CMD_PUSH (отправка данных) IKCP_CMD_ACK (подтверждение) IKCP_CMD_WASK (обнаружение окна) IKCP_CMD_WINS (ответ окна), определенный в ikcp.c:29
Вызовите ikcp_input на k2: (рис. шаг 2-1)
Введите пакет данных [PSH sn=0 frg=2 len=1376]
, проанализируйте заголовок пакета и проверьте достоверность. (ikcp.c:769)
Проанализируйте тип пакета данных и введите обработку ветки принудительной отправки данных. (ikcp.c:822)
Запишите значение seq.sn
и значение seq.ts
пакета данных в список подтверждений kcp.acklist
(ikcp.c:828). Обратите внимание : значение seq.ts
в этом примере всегда равно 0.
Добавьте полученные пакеты в очередь kcp.rcv_buf
. (иккп:709)
Проверьте, доступен ли первый пакет данных в очереди kcp.rcv_buf
. Если это доступный пакет данных, он перемещается в очередь kcp.rcv_queue
. (ikcp.c:726)
Доступные пакеты данных в kcp.rcv_buf
определяются как: следующий порядковый номер данных, который ожидается получить (взятый из kcp.rcv_nxt
, где следующий порядковый номер данных должен быть seg.sn
== 0) и длина kcp.rcv_queue
Очередь kcp.rcv_queue
меньше полученного размера окна.
На этом этапе единственный пакет данных в очереди kcp.rcv_buf
непосредственно перемещается в очередь kcp.rcv_queue
.
В это время длина очереди kcp.>rcv_queue
k2 равна 1, а длина очереди kcp.snd_buf
равна 0. Значение следующего порядкового номера полученных данных kcp.rcv_nxt
обновляется с 0 до 1.
Вызовите ikcp_flush на k2: (Рис. Шаг 2-2)
При первом вызове ikcp_flush со стороны k2. Поскольку в списке подтверждений kcp.acklist
есть данные, пакет подтверждения будет закодирован и отправлен (ikcp.c:958).
Значение seg.una
в пакете подтверждения присваивается kcp.rcv_nxt
=1.
Этот пакет записывается как [ACK sn=0 una=1]
: это означает, что в подтверждении подтверждения подтверждается порядковый номер пакета 0. При неподтверждении все пакеты до пакета номер 1 подтверждаются.
На шаге 2–3 вызывается kcp.output
для отправки пакета данных.
Вызовите ikcp_recv на k2: (рис. шаг 2-4)
Проверьте, содержит ли очередь kcp.rcv_queue
пакет со значением seg.frp
, равным 0 (ikcp.c:459). Если она содержит этот пакет, запишите первый пакет seg.frp
== 0 и данные пакета перед ним. этого пакета. Общая длина возвращается в качестве возвращаемого значения. Если нет, эта функция возвращает значение ошибки -1.
Поскольку в данный момент kcp.rcv_queue
содержит только пакет [PSH sn=0 frg=2 len=1376]
, попытка чтения не удалась.
Если он находится в потоковом режиме (kcp.stream != 0), все пакеты будут помечены как
seg.frg=0
. В это время любые пакеты в очередиkcp.rcv_queue
будут успешно прочитаны.
Вызовите ikcp_input: на k1 (рис. шаг 3-1).
Входной пакет [ACK sn=0 una=1]
.
УНА подтверждает :
Любой полученный пакет сначала попытается подтвердить UNA (ikcp.c:789).
Подтверждены и удалены все пакеты в очереди kcp.snd_buf
значение seg.sn
меньше значения una (ikcp:599), путем подтверждения значения seg.una
пакета.
[PSH sn=0 frg=2 len=1376]
подтвержден и удален из очереди kcp.snd_buf
k1.
ACK-подтверждение :
Проанализируйте тип пакета данных и введите обработку ветки подтверждения. (ikcp.c:792)
Сопоставьте порядковые номера пакетов подтверждения и удалите соответствующие пакеты. (ikcp.c:581)
При выполнении подтверждения ACK на шаге 3-1 очередь kcp.snd_buf
уже пуста, поскольку единственный пакет [PSH sn=0 frg=2 len=1376]
был заранее подтвержден UNA.
Если данные заголовка очереди kcp.snd_buf
подтверждены (изменение kcp.snd_una
), значение cwnd размера окна перегрузки пересчитывается и обновляется до 2 (ikcp.c:876).
Диаграмма подтверждения UNA/ACK, эта диаграмма дополнительно записывает состояние неотмеченного kcp.snd_una
в диаграмме процесса:
Подтверждение ACK не будет работать для пакетов подтверждения, поступающих последовательно. Для пакетов, поступающих не по порядку, пакет удаляется индивидуально после подтверждения через ACK:
Вызовите ikcp_flush на k1: (рис. шаг 3-2)
Как и в шаге 1–2, значение нового окна перегрузки kcp.cwnd
было обновлено до 2, и на этот раз будут отправлены оставшиеся два пакета: [PSH sn=1 frg=1 len=1376]
[PSH sn=2 frg=0 len=1344]
.
На шаге 3–3 kcp.output
фактически будет вызываться дважды для отправки пакетов данных соответственно.
Вызовите ikcp_input: на k2 (рис. шаг 4-1).
Входные пакеты [PSH sn=1 frg=1 len=1376]
и [PSH sn=2 frg=0 len=1344]
.
Каждый пакет добавляется в очередь kcp.rcv_buf
, он доступен, и в конечном итоге все перемещается в очередь kcp.rcv_queue
.
В это время длина очереди kcp.rcv_queue
k2 равна 3, а длина kcp.snd_buf
равна 0. Значение kcp.rcv_nxt
для следующего ожидаемого пакета обновляется с 1 до 3.
Вызовите ikcp_flush на k2: (рис. шаг 4-2)
Информация о подтверждении в kcp.acklist
будет закодирована в пакеты [ACK sn=1 una=3]
и [ACK sn=2 una=3]
и отправлена на шаге 4-3.
Фактически, эти два пакета будут записаны в буфер и будет выполнен вызов kcp.output
.
Вызовите ikcp_recv на k2: (рис. шаг 4-4)
Теперь в kcp.rcv_queue
есть три непрочитанных пакета: [PSH sn=0 frg=2 len=1376]
[PSH sn=1 frg=1 len=1376]
и [PSH sn=2 frg=0 len=1344]
В это время считывается пакет со значением seg.frg
, равным 0, и общая читаемая длина рассчитывается как 4096. Затем все данные в трех пакетах будут прочитаны и записаны в буфер чтения, и будет возвращен успех.
Необходимо обратить внимание на другую ситуацию : если очередь kcp.rcv_queue
содержит 2 отправленных пользователем пакета со значением seg.frg
2/1/0/2/1/0 и фрагментирована на 6 пакетов данных, соответствующий также необходимо дважды вызвать ikcp_recv, чтобы считать все полученные данные полностью.
Вызовите ikcp_input: на k1 (рис. шаг 5-1).
Введите пакеты подтверждения [ACK sn=1 una=3]
и [ACK sn=2 una=3]
и выполните синтаксический анализ до seg.una
=3. Пакет [PSH sn=1 frg=1 len=1376]
[PSH sn=2 frg=0 len=1344]
подтвержден и удален из очереди kcp.snd_buf
через una.
Все отправленные данные подтверждены.
Окно используется для управления потоком. Он отмечает логический диапазон очереди. В результате обработки фактических данных позиция очереди продолжает перемещаться на верхнюю позицию порядкового номера. Логично, что это окно будет продолжать двигаться, расширяться и сжиматься одновременно, поэтому его еще называют скользящим окном .
Эта схематическая диаграмма является еще одним представлением шагов с 3-1 по 4-1 блок-схемы в разделе «Основной процесс отправки и получения данных». В случае операций, выходящих за рамки шагов, направления данных указываются полупрозрачными стрелками.
Все данные обрабатываются через функцию, указанную стрелкой, и перемещаются в новое место (размер изображения в два раза больше):
Данные, переданные функцией ikcp_send на стороне отправки, будут непосредственно сохранены в очереди отправки kcp.snd_queue
после обработки среза данных.
каждый раз, когда вызывается ikcp_flush . Размер окна для этой передачи будет рассчитываться на основе размера окна отправки kcp.snd_wnd
, размера удаленного окна kcp.rmt_wnd
и размера окна перегрузки kcp.cwnd
Это значение является минимальным из трех: min( kcp.snd_wnd
, kcp.rmt_wnd
, kcp.cwd
) (ikcp.c:1017).
Если параметр nc установлен в 1 через функцию ikcp_nodelay и режим управления потоком выключен, расчет значения окна перегрузки игнорируется. Результатом расчета окна отправки является min( kcp.snd_wnd
, kcp.rmt_wnd
) (ikcp.c:1018).
В конфигурации по умолчанию, при которой отключается только режим управления потоком, количество пакетов данных, которые могут быть отправлены в первый раз, соответствует значению размера по умолчанию kcp.snd_wnd
32. Это отличается от примера базового процесса отправки и получения, в котором только один пакет может быть отправлен в первый раз, поскольку управление потоком включено по умолчанию.
Вновь отправленные пакеты данных будут перемещены в очередь kcp.snd_buf
.
Для данных ikcp_send существует ограничение только на размер фрагмента 127 (т. е. 127*
kcp.mss
=174752 байта). Общее количество пакетов в очереди отправки не ограничено. См.: Как избежать задержек накопления кэша.
Буфер отправки kcp.snd_buf
хранит данные, которые будут или уже были отправлены.
При каждом вызове ikcp_flush
вычисляется окно отправки и пакет данных перемещается из kcp.snd_queue
в текущую очередь. Все пакеты данных в текущей очереди будут обработаны в трех ситуациях:
1. Первая отправка данных (ikcp.c:1053)
Количество отправлений пакета будет записано в seg.xmit
. Обработка первой отправки относительно проста, и будут инициализированы некоторые параметры seg.rto
/ seg.resendts
для таймаута повторной передачи.
2. Тайм-аут данных (ikcp.c:1058)
Когда внутренне записанное время kcp.current
превышает период ожидания seg.resendts
самого пакета, происходит повторная передача по тайм-ауту.
3. Подтверждение пересечения данных (ikcp.c:1072).
Когда конец данных охватывается и количество интервалов seg.fastack
превышает конфигурацию повторной передачи диапазона kcp.fastresend
, происходит повторная передача диапазона. ( По умолчанию kcp.fastresend
имеет значение 0, а когда оно равно 0, оно рассчитывается как UINT32_MAX, и повторная передача диапазона никогда не произойдет.) По истечении времени повторной передачи текущий пакет seg.fastack
будет сброшен в 0.
Список подтверждений — это простой список записей, seg.ts
котором изначально записываются порядковые номера и временные метки ( seg.sn
) в порядке получения пакетов.
Таким образом, на схематической диаграмме, представленной в этой статье,
kcp.ack_list
не будет нарисовано пустых позиций элементов. Потому что это не логически упорядоченная очередь (аналогично, хотя пакету в очередиsnd_queue
еще не присвоен порядковый номер, его логический порядковый номер уже определен).
Принимающая сторона буферизует пакеты данных, которые временно не могут быть обработаны.
Все пакеты данных, поступающие из ikcp_input, будут поступать в эту очередь первыми, а информация будет записываться в kcp.ack_list
в исходном порядке поступления.
Есть только две ситуации, когда данные все равно останутся в этой очереди:
Здесь первым принимается пакет [PSH sn=0]
, который соответствует условиям доступных пакетов и перемещается в kcp.rev_queue
.
Затем был получен пакет [PSH sn=2]
, который не был следующим ожидаемым пакетом ( seg.sn
== kcp.rcv_nxt
), в результате чего этот пакет остался в kcp.rcv_buf
.
После получения пакета [PSH sn=1]
переместите два застрявших пакета [sn=1]
[sn=2]
в kcp.rcv_queue
.
kcp.rcv_queue
достигает размера окна приема kcp.rcv_wnd
(ikcp_recv не был вызван вовремя).Принимающая сторона хранит данные, которые может быть прочитан верхним уровнем.
В потоковом режиме считываются все доступные пакеты, в непотоковом режиме считываются фрагментированные сегменты данных и собираются в полные необработанные данные.
После завершения чтения будет предпринята попытка переместить данные из kcp.rcv_buf
в эту очередь (возможно, для восстановления из состояния полного окна приема).
Значение kcp.snd_wnd
окна отправки — это настроенное значение, значение по умолчанию — 32.
Удаленное окно kcp.rmt_wnd
— это значение, которое будет обновляться, когда отправитель получает пакет от получателя (а не только пакет подтверждения). Он записывает доступную длину (ikcp.c:1086) очереди приема принимающей стороны kcp.rcv_queue
, когда текущий пакет данных отправляется принимающей стороной. Начальное значение равно 128.
Окно перегрузки — это расчетное значение, которое алгоритмически увеличивается каждый раз, когда данные принимаются через ikcp_input.
Если при сбросе данных ikcp_flush будут обнаружены потеря пакетов и быстрая повторная передача, то они будут пересчитаны согласно алгоритму.
Позиция, в которой
kcp.cwnd
инициализируется значением 1, находится в первом вызове ikcp_update ikcp_flush.
Значение kcp.rcv_wnd
окна приема — это настроенное значение, значение по умолчанию — 128. Он ограничивает максимальную длину очереди приема kcp.rcv_queue
.
В этом разделе представлена улучшенная версия kcp_optional.c на основе примера кода из раздела «Основная отправка и получение данных». Вы можете дополнительно наблюдать за поведением протокола, изменив определение макроса.
Пример кода завершает процесс, указывая записать указанное количество данных фиксированной длины в k1 и полностью прочитать их в k2.
Макросы предназначены для управления указанными функциями:
Окно перегрузки записывается через значения kcp.cwnd
и kcp.incr
. Поскольку единицей измерения, записанной в kcp.cwnd
, является пакет, для записи окна перегрузки, выраженного в единицах длины в байтах, требуется дополнительный kcp.incr
.
Как и TCP, контроль перегрузки KCP также разделен на два этапа: медленный запуск и предотвращение перегрузки:
Окно перегрузки увеличивается ; в процессе подтверждения пакета данных каждый раз подтверждаются данные заголовка очереди kcp.snd_buf
(действующее подтверждение UNA, kcp.snd_una
меняется). А когда окно перегрузки меньше записанного удаленного окна kcp.rmt_wnd
, окно перегрузки увеличивается. (иккп:875)
1. Если окно перегрузки меньше порогового значения медленного запуска kcp.ssthresh
, оно находится на стадии медленного запуска , и рост окна перегрузки в это время относительно агрессивен. Окно перегрузки увеличивается на одну единицу.
2. Если окно перегрузки больше или равно порогу медленного старта, то это стадия предотвращения перегрузки , и рост окна перегрузки является относительно консервативным. Если kcp.incr
каждый раз увеличивает mss/16, потребуется 16 действительных подтверждений UNA, прежде чем окно перегрузки устройства будет увеличено. Фактический рост окна фазы предотвращения перегрузок составляет:
(mss * mss) / incr + (mss / 16)
Поскольку incr=cwnd*mss:
((mss * mss) / (cwnd * mss)) + (mss / 16)
Эквивалентно:
(mss / cwnd) + (mss / 16)
Окно перегрузки постепенно увеличивается для каждого cwnd и каждых 16 действительных подтверждений UNA.
Уменьшение окна перегрузки : когда функция ikcp_flush обнаруживает потерю пакетов при повторных передачах или тайм-аутах, окно перегрузки уменьшается.
1. При повторной передаче диапазона порог медленного запуска kcp.ssthresh
устанавливается равным половине диапазона неподтвержденного порядкового номера. Размер окна перегрузки — это порог медленного запуска плюс значение конфигурации быстрой повторной передачи kcp.resend
(ikcp:1117):
ssthresh = (snd_nxt - snd_una) / 2
cwnd = ssthresh + resend
2. При обнаружении тайм-аута потери пакетов порог медленного запуска устанавливается равным половине текущего окна перегрузки. Окно перегрузки установлено на 1 (ikcp:1126):
ssthresh = cwnd / 2
cwnd = 1
Наблюдайте за медленным запуском 1. Запустите пример кода с конфигурацией по умолчанию, и вы увидите, что процесс медленного запуска быстро пропускается. Это связано с тем, что порог медленного запуска по умолчанию равен 2:
t=0 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=2 incr=1376
# 收到一个确认包且拥塞窗口小于慢启动阈值,incr 增加一个 mss
t=100 n=1 una=1 nxt=2 cwnd=2|2 ssthresh=2 incr=2752
# 开始拥塞避免
t=200 n=1 una=2 nxt=3 cwnd=2|2 ssthresh=2 incr=3526
t=300 n=1 una=3 nxt=4 cwnd=4|4 ssthresh=2 incr=4148
t=400 n=1 una=4 nxt=5 cwnd=4|4 ssthresh=2 incr=4690
...
t в выходном содержимом — это логическое время, n — это количество раз, когда k1 отправляет данные в цикле, значение cwnd=1|1 указывает, что 1 перед символом вертикальной полосы — это окно, рассчитанное при ikcp_flush, что is, min(kcp. в режиме управления потоком. snd_wnd, kcp.rmt_wnd, kcp.cwnd), значение следующей единицы равно kcp.cwnd
.
Наблюдайте за ростом окна перегрузки в конфигурации по умолчанию графически: на этапе предотвращения перегрузки чем больше окно перегрузки, тем плавнее рост окна перегрузки.
Соблюдайте медленный запуск 2 : установите начальное значение порога медленного запуска образца конфигурации KCP_THRESH_INIT на 16:
#define KCP_THRESH_INIT 16
t=0 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=16 incr=1376
t=100 n=1 una=1 nxt=2 cwnd=2|2 ssthresh=16 incr=2752
t=200 n=1 una=2 nxt=3 cwnd=3|3 ssthresh=16 incr=4128
t=300 n=1 una=3 nxt=4 cwnd=4|4 ssthresh=16 incr=5504
...
t=1300 n=1 una=13 nxt=14 cwnd=14|14 ssthresh=16 incr=19264
t=1400 n=1 una=14 nxt=15 cwnd=15|15 ssthresh=16 incr=20640
t=1500 n=1 una=15 nxt=16 cwnd=16|16 ssthresh=16 incr=22016
# 开始拥塞避免
t=1600 n=1 una=16 nxt=17 cwnd=16|16 ssthresh=16 incr=22188
t=1700 n=1 una=17 nxt=18 cwnd=16|16 ssthresh=16 incr=22359
...
Перехватывая только короткий период перед раундом передачи, можно заметить, что медленный старт также увеличивается линейным образом по умолчанию.
Обратите внимание на отключение отложенного подтверждения : отправьте как можно больше данных, отключите опцию отложенной отправки ACK_DELAY_FLUSH и смоделируйте потерю пакета:
#define KCP_WND 256, 256
#define KCP_THRESH_INIT 32
#define SEND_DATA_SIZE (KCP_MSS*64)
#define SEND_STEP 16
#define K1_DROP_SN 384
//#define ACK_DELAY_FLUSH
t=0 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=32 incr=1376
t=100 n=2 una=1 nxt=3 cwnd=2|2 ssthresh=32 incr=2752
t=200 n=4 una=3 nxt=7 cwnd=4|4 ssthresh=32 incr=5504
t=300 n=8 una=7 nxt=15 cwnd=8|8 ssthresh=32 incr=11008
t=400 n=16 una=15 nxt=31 cwnd=16|16 ssthresh=32 incr=22016
...
t=1100 n=52 una=269 nxt=321 cwnd=52|52 ssthresh=32 incr=72252
t=1200 n=56 una=321 nxt=377 cwnd=56|56 ssthresh=32 incr=78010
t=1300 n=62 una=377 nxt=439 cwnd=62|62 ssthresh=32 incr=84107
t=1400 n=7 una=384 nxt=446 cwnd=62|62 ssthresh=32 incr=84863
t=1500 n=1 una=384 nxt=446 cwnd=1|1 ssthresh=31 incr=1376
t=1600 n=2 una=446 nxt=448 cwnd=2|2 ssthresh=31 incr=2752
t=1700 n=4 una=448 nxt=452 cwnd=4|4 ssthresh=31 incr=5504
t=1800 n=8 una=452 nxt=460 cwnd=8|8 ssthresh=31 incr=11008
t=1900 n=16 una=460 nxt=476 cwnd=16|16 ssthresh=31 incr=22016
...
В этом случае получается классический график медленного старта и предотвращения перегрузок. Потеря пакетов была обнаружена на 15-м раунде передачи (t=1500):
Отключение опции отложенной отправки означает, что сторона-получатель данных будет вызывать ikcp_flush сразу после каждого выполнения ikcp_input для отправки обратно пакета подтверждения.
Процесс медленного запуска в это время увеличивается экспоненциально с каждым RTT (время приема-передачи, время приема-передачи), поскольку каждый пакет подтверждения будет отправляться независимо, что приводит к увеличению окна перегрузки отправителя, а поскольку окно перегрузки растет, количество пакетов увеличивается. отправленных в рамках каждого RTT, увеличится вдвое.
Если подтверждение задерживается, пакет подтверждения будет отправлен вместе. Процесс увеличения окна перегрузки будет выполняться только один раз при каждом вызове функции ikcp_input, поэтому объединение нескольких полученных пакетов подтверждения не приведет к многократному увеличению окна перегрузки.
Если интервал тактового цикла больше, чем RTT, он будет экспоненциально увеличиваться с каждым интервалом. На схематической диаграмме показана возможная ситуация:
Предпосылка экспоненциального роста заключается в том, что данные, отправленные в следующий раз, могут удовлетворить вдвое большее количество пакетов данных в прошлый раз. Если данных, записанных на отправляющую сторону, недостаточно, экспоненциальный рост не будет достигнут.
Следует отметить, что даже если пример кода отправляет немедленное подтверждение, это влияет только на способ отправки подтверждения принимающей стороной. Отправителю также необходимо дождаться следующего цикла перед обработкой этих пакетов подтверждения. Следовательно, время t здесь приведено только для справки, если полученный пакет не обработан и не сохранен немедленно в реальном коде сетевого приемопередатчика, он должен дождаться цикла обновления, прежде чем ответить.
Основываясь на характеристиках KCP, должен быть более прямой способ увеличить окно перегрузки и напрямую отключить управление потоком, чтобы получить более агрессивную отправку:
#define KCP_NODELAY 0, 100, 0, 1
#define SEND_DATA_SIZE (KCP_MSS*127)
#define SEND_STEP 1
t=0 n=32 una=0 nxt=32 cwnd=32|1 ssthresh=2 incr=1376
t=100 n=32 una=32 nxt=64 cwnd=32|2 ssthresh=2 incr=2752
t=200 n=32 una=64 nxt=96 cwnd=32|2 ssthresh=2 incr=3526
t=300 n=31 una=96 nxt=127 cwnd=32|4 ssthresh=2 incr=4148
При необходимости вы также можете напрямую изменить значение окна перегрузки.
Обратите внимание, что удаленное окно заполнено : если длина отправленных данных близка к размеру удаленного окна по умолчанию и принимающая сторона не считывает их вовремя, вы обнаружите период, в течение которого никакие данные не могут быть отправлены (обратите внимание, что в примере кода принимающая сторона сначала отправляет пакет подтверждения, а затем снова считывает содержимое):
#define KCP_NODELAY 0, 100, 0, 1
#define SEND_DATA_SIZE (KCP_MSS*127)
#define SEND_STEP 2
t=0 n=32 una=0 nxt=32 cwnd=32|1 ssthresh=2 incr=1376
t=100 n=32 una=32 nxt=64 cwnd=32|2 ssthresh=2 incr=2752
t=200 n=32 una=64 nxt=96 cwnd=32|2 ssthresh=2 incr=3526
t=300 n=32 una=96 nxt=128 cwnd=32|4 ssthresh=2 incr=4148
t=400 n=0 una=128 nxt=128 cwnd=0|4 ssthresh=2 incr=4148
t=500 n=32 una=128 nxt=160 cwnd=32|4 ssthresh=2 incr=4148
t=600 n=32 una=160 nxt=192 cwnd=32|4 ssthresh=2 incr=4690
t=700 n=32 una=192 nxt=224 cwnd=32|4 ssthresh=2 incr=5179
t=800 n=30 una=224 nxt=254 cwnd=31|4 ssthresh=2 incr=5630
Когда размер удаленного окна kcp.rmt_wnd
записанный в приемнике, равен 0, фаза ожидания зонда (ожидание зонда, ikcp.c:973) будет запущена в ikcp_flush. kcp.ts_probe
первоначально записывает время в 7000 миллисекунд (записывается в kcp->probe_wait
).
Когда придет время, дополнительно закодируйте пакет типа IKCP_CMD_WASK и отправьте его принимающей стороне (ikcp.c:994), чтобы запросить удаленную сторону отправить обратно пакет типа IKCP_CMD_WINS для обновления kcp.rmt_wnd
Если размер удаленного окна всегда равен 0, kcp->probe_wait
каждый раз увеличивается на половину текущего значения, а затем обновляет время ожидания. Максимальное время ожидания составляет 120 000 миллисекунд (120 секунд).
Если размер удаленного окна не равен 0, указанный выше статус обнаружения очищается.
В этом примере мы не ждем первых 7 секунд, прежде чем восстановить записанный размер удаленного окна. Поскольку при операции ikcp_recv на принимающей стороне для чтения данных, когда длина очереди kcp.rcv_queue
больше или равна окну приема kcp.rcv_wnd
перед чтением данных (окно чтения заполнено), бит флага (ikcp.c:431) и в следующий раз отправьте пакет типа IKCP_CMD_WINS (ikcp.c:1005) ikcp_flush Чтобы уведомить отправляющую сторону об обновлении последнего размера удаленного окна.
Чтобы избежать этой проблемы, данные необходимо своевременно считывать на принимающей стороне. Однако даже если удаленное окно становится меньше, окно отправки отправителя станет меньше, что приведет к дополнительным задержкам. При этом также необходимо увеличить окно приема принимающей стороны.
Попробуйте изменить значение RECV_TIME на относительно большое значение (например, 300 секунд), а затем наблюдать за отправкой пакета IKCP_CMD_WASK.
Как описано в описании очереди kcp.snd_buf
, при вызове ikcp_flush будут пройдены все пакеты в очереди, если пакет не отправляется в первый раз. Затем он проверит, был ли пакет пересечен с подтверждением указанное количество раз или достигнут ли период таймаута.
Поля, относящиеся к расчету времени туда и обратно и тайм-аута:
kcp.rx_rttval
: плавное время дрожания сети.kcp.rx_srtt
: Плавное время прохождения туда и обратно.kcp.rx_rto
(Receive Retransmission Timeout): тайм-аут повторной передачи, начальное значение 200kcp.rx_minrto
: Минимальное время ожидания повторной передачи, начальное значение 100.kcp.xmit
: глобальное количество повторных передачseg.resendts
: временная метка повторной передачиseg.rto
: тайм-аут повторной передачиseg.xmit
: количество повторных передачПрежде чем обсуждать, как пакет вычисляет таймауты, давайте сначала посмотрим, как вычисляются связанные поля времени туда и обратно и таймаута:
Запись времени туда и обратно : каждый раз, когда обрабатывается пакет подтверждения ACK, пакет подтверждения будет содержать порядковый номер и время, когда порядковый номер был отправлен отправителю ( seg.sn
/ seg.ts
). выполняется процесс обновления времени.
Значение RTT - это время обратного пути одного пакета, то есть RTT = kcp.current
- seg.ts
Если сглаженное время обработки обработки kcp.rx_srtt
равно 0, это означает, что выполняется инициализация: kcp.rx_srtt
непосредственно записывается как rtt, kcp.rx_rttval
записывается как половина RTT.
В процессе не инициализации рассчитывается значение дельты, которое представляет значение колебания этого RTT и записанный kcp.rx_srtt
(ikcp.c: 550):
delta = abs(rtt - rx_srtt)
Новый kcp.rx_rttval
обновляется взвешенным значением старого kcp.rx_rttval
и delta:
rx_rttval = (3 * rx_rttval + delta) / 4
Новый kcp.rx_srtt
обновляется взвешенным значением старого kcp.rx_srtt
и rtt, и не может быть меньше 1:
rx_srtt = (7 * rx_srtt + rtt) / 8
rx_srtt = max(1, rx_srtt)
Новый rx_rto
обновляется минимальным значением сглаженного времени круговой поездки kcp.rx_srtt
плюс тактовой цикл kcp.interval
и 4 раза rx_rttval
, а диапазон ограничен [ kcp.rx_minrto
, 60000]::
rto = rx_srtt + max(interval, 4 * rttval)
rx_rto = min(max(`kcp.rx_minrto`, rto), 60000)
В идеале, когда сеть имеет только фиксированную задержку, а не джиттер, значение kcp.rx_rttval
будет приближаться к 0, а значение kcp.rx_rto
определяется плавным временем и тактовым циклом обратного пути.
Гладкая диаграмма расчета времени в обратном пути:
Первое время доставки контракта (IKCP.C: 1052):
seg.rto
будет записывать статус kcp.rx_rto
, а первое время аута пакета данных - seg.rto
+ RTOMIN MILLISECONDS.
Rtomin рассчитывается с помощью kcp.rx_rto
, если режим Nodelay включен. Rtomin - это 0, в противном случае kcp.rx_rto
/8.
Тайм -аут для Nodelay не включен:
resendts = current + rx_rto + rx_rto / 8
Включить тайм -аут Nodelay:
resendts = current + rx_rto
Повторная передача тайм -аута (ikcp.c: 1058):
Когда внутреннее время достигает времени времени ожидания seg.resendts
пакета данных, пакет с этим номером последовательности повторяется.
Когда режим Nodelay не включен, увеличение seg.rto
- MAX ( seg.rto
, kcp.rx_rto
) (двойной рост):
rto += max(rto, rx_rto)
Когда Nodelay включен, а Nodelay - 1, увеличивайте seg.rto
на половину каждый раз (в 1,5 раза увеличивается):
rto += rto / 2
Когда Nodelay включен, а Nodelay - 2, kcp.rx_rto
увеличивается в половину каждый раз (увеличивается в 1,5 раза):
rto += rx_rto / 2
Новый тайм -аут после seg.rto
MILLISECONDS:
resendts = current + rx_rto
Повторная передача во времени (IKCP.C: 1072):
Когда пакет данных пересекает указанное количество раз, запускается ретрансмиссия пересечения.
seg.rto
не обновляется при повторном поступлении во времени, а время ретрансмиссии в следующее время выходит непосредственно:
resendts = current + rto
Наблюдайте за время ожидания по умолчанию
Отправьте только один пакет, бросьте его четыре раза и соблюдайте время ожидания и ретрансмиссии.
Для конфигурации по умолчанию первоначальное значение kcp.rx_rto
составляет 200 миллисекунд, а в первом времени - 225 миллисекунд. .
#define SEND_STEP 1
#define K1_DROP_SN 0,0,0,0
t=0 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=2 incr=1376
...
t=300 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=2 incr=1376
...
t=700 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=2 incr=1376
...
t=1500 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=2 incr=1376
...
t=3100 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=2 incr=1376
Наблюдайте за увеличением RTO на 1,5 раза на основе самого пакета
#define KCP_NODELAY 1, 100, 0, 0
#define SEND_STEP 1
#define K1_DROP_SN 0,0,0,0
t=0 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=2 incr=1376
...
t=200 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=2 incr=1376
...
t=500 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=2 incr=1376
...
t=1000 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=2 incr=1376
...
t=1700 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=2 incr=1376
Смотреть на 1,5 -кратный рост на основе RTO
#define KCP_NODELAY 2, 100, 0, 0
#define SEND_STEP 1
#define K1_DROP_SN 0,0,0,0
t=0 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=2 incr=1376
...
t=200 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=2 incr=1376
...
t=500 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=2 incr=1376
...
t=900 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=2 incr=1376
...
t=1400 n=1 una=0 nxt=1 cwnd=1|1 ssthresh=2 incr=1376
Наблюдать за ретрансмиссиями SPAN
Пакеты [sn=1]
[sn=2]
обработанные слиянием [sn=0]
не запускают ретрансляцию пролета. В конце концов, он был передан через тайм -аут.
#define KCP_NODELAY 0, 100, 2, 1
#define SEND_DATA_SIZE (KCP_MSS*3)
#define SEND_STEP 1
#define K1_DROP_SN 0
t=0 n=3 una=0 nxt=3 cwnd=32|1 ssthresh=2 incr=1376
t=100 n=0 una=0 nxt=3 cwnd=32|1 ssthresh=2 incr=1376
t=200 n=0 una=0 nxt=3 cwnd=32|1 ssthresh=2 incr=1376
t=300 n=1 una=0 nxt=3 cwnd=32|1 ssthresh=16 incr=1376
При отправке пакетов данных за два шага вторая группа пакетов будет дважды пересекаться при выполнении подтверждения IKCP_INPUT, а [sn=0]
будет накоплена и повторно передана в течение следующего IKCP_FLUSH.
#define KCP_NODELAY 0, 100, 2, 1
#define SEND_DATA_SIZE (KCP_MSS*2)
#define SEND_STEP 2
#define K1_DROP_SN 0
t=0 n=2 una=0 nxt=2 cwnd=32|1 ssthresh=2 incr=1376
t=100 n=2 una=0 nxt=4 cwnd=32|1 ssthresh=2 incr=1376
t=200 n=1 una=0 nxt=4 cwnd=32|4 ssthresh=2 incr=5504
t=300 n=0 una=4 nxt=4 cwnd=32|4 ssthresh=2 incr=5934
Статья лицензирована по международной лицензии Creative Commons Attribution-Noncommercial-Noderivs 4.0.
Код в проекте является открытым исходным кодом с использованием лицензии MIT.
О изображении шрифт: нотока Sans SC