Em certas aplicações, o simples uso do TCP não pode atender às necessidades. O uso direto de datagramas UDP não pode garantir a confiabilidade dos dados, e muitas vezes é necessário implementar um protocolo de transmissão confiável baseado em UDP na camada de aplicação.
O uso direto do protocolo KCP é uma opção, que implementa um protocolo robusto de retransmissão automática e fornece ajuste gratuito de parâmetros sobre ele. Adapte-se às necessidades de diferentes cenários através de parâmetros de configuração e métodos de chamada apropriados.
Introdução ao KCP:
KCP é um protocolo rápido e confiável que pode reduzir o atraso médio em 30% a 40% e reduzir o atraso máximo em três vezes ao custo de 10% a 20% mais largura de banda que o TCP. A implementação pura do algoritmo não é responsável pelo envio e recebimento de protocolos subjacentes (como UDP). Os usuários precisam definir o método de envio dos pacotes de dados da camada inferior e fornecê-los ao KCP na forma de retorno de chamada. Até o relógio precisa ser transmitido externamente e não haverá nenhuma chamada de sistema internamente. Todo o protocolo possui apenas dois arquivos de origem, ikcp.h e ikcp.c, que podem ser facilmente integrados à pilha de protocolos do próprio usuário. Talvez você tenha implementado um protocolo P2P ou baseado em UDP, mas não possui uma implementação completa e confiável do protocolo ARQ. Em seguida, basta copiar esses dois arquivos para o projeto existente, escrever algumas linhas de código e você poderá usá-lo.
Este artigo apresenta brevemente o processo básico de envio e recebimento, janela de congestionamento e algoritmo de tempo limite do protocolo KCP e também fornece código de amostra de referência.
A versão do KCP referenciada é a versão mais recente no momento da redação deste artigo. Este artigo não colará completamente todo o código-fonte do KCP, mas adicionará links para os locais correspondentes do código-fonte em pontos-chave.
A estrutura IKCPSEG é usada para armazenar o status dos segmentos de dados enviados e recebidos.
Descrição de todos os campos 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];
};
O campo data
no final da estrutura é usado para indexar os dados no final da estrutura. A memória alocada adicional estende o comprimento real da matriz do campo de dados em tempo de execução (ikcp.c:173).
A estrutura IKCPSEG é apenas de estado de memória e apenas alguns campos são codificados no protocolo de transporte.
A função ikcp_encode_seg codifica o cabeçalho do protocolo de transporte:
/* 协议头一共 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;
}
A estrutura IKCPCB armazena todo o contexto do protocolo KCP, e a comunicação do protocolo é realizada criando dois objetos IKCPCB na extremidade oposta.
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;
Existem apenas 2 estruturas de fila no KCP:
IQUEUEHEAD é uma lista duplamente vinculada simples que aponta para o início (anterior) e o último (próximo) elemento da fila:
struct IQUEUEHEAD {
/*
next:
作为队列时: 队列的首元素 (head)
作为元素时: 当前元素所在队列的下一个节点
prev:
作为队列时: 队列的末元素 (last)
作为元素时: 当前元素所在队列的前一个节点
*/
struct IQUEUEHEAD *next, *prev;
};
typedef struct IQUEUEHEAD iqueue_head;
Quando a fila estiver vazia, next/prev apontará para a própria fila, não para NULL.
O cabeçalho da estrutura IKCPSEG como elemento de fila também reutiliza a estrutura IQUEUEHEAD:
struct IKCPSEG
{
struct IQUEUEHEAD node;
/* ... */
}
Quando usado como um elemento de fila, o elemento anterior (anterior) e o próximo elemento (próximo) na fila onde o elemento atual está localizado são registrados.
Quando prev aponta para a fila, significa que o elemento atual está no início da fila, e quando next aponta para a fila, significa que o elemento atual está no final da fila.
Todas as operações de fila são fornecidas como macros.
Os métodos de configuração fornecidos pelo KCP são:
Opções de modo de trabalho :
int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc)
Opções máximas de janela :
int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
O tamanho da janela de envio sndwnd
deve ser maior que 0 e o tamanho da janela de recebimento rcvwnd
deve ser maior que 128. A unidade são pacotes, não bytes.
Unidade máxima de transmissão :
O KCP não é responsável por detectar MTU. O valor padrão é 1400 bytes. Você pode usar ikcp_setmtu para definir esse valor. Este valor afetará a unidade máxima de transmissão ao combinar e fragmentar pacotes de dados. Um MTU menor afetará a prioridade de roteamento.
Este artigo fornece um código kcp_basic.c que pode basicamente executar o KCP. O código de amostra com menos de 100 linhas é uma chamada de algoritmo pura para o KCP e não inclui nenhum agendamento de rede. ( Importante : siga o artigo para depurá-lo e experimente!)
Você pode usá-lo para obter uma compreensão preliminar dos campos básicos da estrutura do IKCPCB:
kcp.snd_queue
: fila de envio (comprimento do registro kcp.nsnd_que
)kcp.snd_buf
: buffer de envio (comprimento do registro kcp.nsnd_buf
)kcp.rcv_queue
: fila de recebimento (comprimento do registro kcp.nrcv_que
)kcp.rcv_buf
: buffer de recebimento (comprimento do registro kcp.nrcv_buf
)kcp.mtu
: unidade máxima de transmissãokcp.mss
: comprimento máximo da mensagemE na estrutura IKCPSEG:
seg.sn
: número de sérieseg.frg
: número do segmentoCrie a estrutura de contexto KCP IKCPCB por meio da função ikcp_create.
O IKCPCB cria internamente a estrutura IKCPSEG correspondente para armazenar dados e status chamando externamente ikcp_send (entrada do usuário para o remetente) e dados de entrada ikcp_input (entrada do remetente para o destinatário).
Além disso, a estrutura IKCPSEG será removida por meio de ikcp_recv (removido pelo usuário da extremidade receptora) e dados de confirmação ikcp_input (recebidos pela extremidade emissora da extremidade receptora).
Para obter instruções detalhadas do fluxo de dados, consulte a seção Fila e janela .
A criação e destruição do IKCPSEG ocorre principalmente nas quatro situações acima, e outras são comuns em movimentos entre filas internas e outras otimizações.
Nos artigos a seguir, todos os campos de estrutura IKCPCB e IKCPSEG que aparecem serão destacados por
标记
(somente navegação de markdown, outros podem não vê-lo). Todos os campos da estrutura IKCPCB serão prefixados comkcp.
e todos os campos da estrutura IKCPSEG serão prefixados comseg.
. Normalmente, o nome da variável ou do parâmetro de função correspondente no código-fonte também ékcp
ouseg
.
Este código simplesmente grava o comprimento de dados especificado no objeto KCP denominado k1 e lê os dados do objeto k2. O KCP está configurado no modo padrão.
O processo básico pode ser descrito simplesmente por meio de pseudocódigo como:
/* 创建两个 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)
No código de amostra, o objeto KCP criado está vinculado à função kcp_user_output além de kcp.output
que é usado para definir o comportamento dos dados de saída do objeto KCP. kcp.writelog
também está vinculado à função kcp_user_writelog para depuração de impressão.
Além disso, como kcp.output
não pode chamar outro ikcp_input recursivamente (porque eventualmente recorrerá ao seu próprio kcp.output
), todos os dados de saída devem ser armazenados em um local intermediário e, em seguida, inseridos em k2 após sair do kcp.output
função. Este é o propósito da estrutura OUTPUT_CONTEXT definida no código de exemplo.
Tente executar o código de exemplo e você obterá a seguinte saída (o conteúdo anexado com o sinal # é uma explicação):
# 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
O conteúdo de saída são as informações de depuração impressas no código KCP. O prefixo da linha k1/k2 é adicionalmente anexado por meio de kcp_user_writelog como uma distinção.
O diagrama esquemático completo do processo de confirmação de envio deste código é descrito como (o dobro do tamanho):
Chame ikcp_send em k1: (Figura etapa 1-1)
Dados com comprimento de 4096 são gravados no remetente. De acordo com kcp.mss
ele é cortado em três pacotes com comprimento de 1376/1376/1344, e seg.frg
de cada pacote são 2/1/0, respectivamente.
A unidade máxima de transmissão kcp.mtu
define o comprimento máximo dos dados recebidos pelo retorno de chamada ikcp.output
a cada vez. O padrão é 1400.
No diagrama esquemático, o método ikcp_output eventualmente chamará o ponteiro de função
ikcp.output
. (ikcp.c:212)
O comprimento máximo da mensagem kcp.mss
é calculado subtraindo a sobrecarga do protocolo (24 bytes) de kcp.mtu
.
Nenhum retorno de chamada kcp.output
será executado neste momento e todos os dados de fragmento serão alocados e registrados na estrutura IKCPSEG e anexados à fila kcp.snd_queue
(ikcp.c:528).
Neste momento, o comprimento da fila kcp.snd_queue
de k1 é 3 e o comprimento da fila kcp.snd_buf
é 0.
Chame ikcp_flush em k1: (Figura etapa 1-2)
O processo de cálculo específico da janela é ignorado aqui. Você só precisa saber que o valor da janela de congestionamento
kcp.cwnd
é 1 quando k1 chama ikcp_flush pela primeira vez.
Devido ao limite da janela de congestionamento, apenas um pacote pode ser enviado pela primeira vez. O objeto IKCPSEG com o primeiro comprimento de dados da fila kcp.snd_queue
é movido para a fila kcp.snd_buf
(ikcp.c:1028) e o valor do número de sequência seg.sn
atribuído de acordo com kcp.snd_nxt
é 0 (ikcp .c:1036) , o campo seg.cmd
é IKCP_CMD_PUSH, Representa um pacote de envio de dados.
Neste momento, o comprimento da fila kcp.snd_queue
de k1 é 2 e kcp.snd_buf
é 1.
Na etapa 1-3, execute a chamada ikcp_output (ikcp.c:1113) nos primeiros dados enviados para enviar o pacote de dados [PSH sn=0 frg=2 len=1376]
.
Existem apenas quatro tipos de comando de dados: IKCP_CMD_PUSH (envio de dados) IKCP_CMD_ACK (confirmação) IKCP_CMD_WASK (detecção de janela) IKCP_CMD_WINS (resposta de janela), definido em ikcp.c:29
Chame ikcp_input em k2: (Figura etapa 2-1)
Insira o pacote de dados [PSH sn=0 frg=2 len=1376]
, analise o cabeçalho do pacote e verifique a validade. (ikcp.c:769)
Analise o tipo de pacote de dados e insira o processamento da ramificação push de dados. (ikcp.c:822)
Registre o valor seq.sn
e o valor seq.ts
do pacote de dados na lista de confirmação kcp.acklist
(ikcp.c:828). Observe : o valor de seq.ts
neste exemplo é sempre 0.
Adicione pacotes recebidos à fila kcp.rcv_buf
. (ikcp:709)
Verifique se o primeiro pacote de dados na fila kcp.rcv_buf
está disponível. Se for um pacote de dados disponível, ele será movido para kcp.rcv_queue
. (ikcp.c:726)
Os pacotes de dados disponíveis em kcp.rcv_buf
são definidos como: o próximo número de sequência de dados que se espera receber (retirado de kcp.rcv_nxt
, onde o próximo número de sequência de dados deve ser seg.sn
== 0) e o comprimento do kcp.rcv_queue
A fila kcp.rcv_queue
é menor que o tamanho da janela recebida.
Nesta etapa, o único pacote de dados na fila kcp.rcv_buf
é movido diretamente para a fila kcp.rcv_queue
.
Neste momento, kcp.>rcv_queue
de k2 é 1 e o comprimento da fila kcp.snd_buf
é 0. O valor do próximo número de sequência de dados recebido kcp.rcv_nxt
é atualizado de 0 para 1.
Chame ikcp_flush em k2: (Fig. Etapa 2-2)
Na primeira chamada ikcp_flush do k2. Como há dados na lista de confirmação kcp.acklist
, o pacote de confirmação será codificado e enviado (ikcp.c:958).
O valor seg.una
no pacote de confirmação é atribuído kcp.rcv_nxt
=1.
Este pacote é registrado como [ACK sn=0 una=1]
: Significa que na confirmação do ack, o número de sequência do pacote 0 é confirmado. Em uma confirmação, todos os pacotes anteriores ao pacote número 1 são confirmados.
Na etapa 2-3, kcp.output
é chamado para enviar o pacote de dados.
Chame ikcp_recv em k2: (Figura Etapa 2-4)
Verifique se a fila kcp.rcv_queue
contém um pacote com seg.frp
de 0 (ikcp.c:459). Se contiver este pacote, registre o primeiro pacote seg.frp
== 0 e os dados do pacote anterior. este pacote. O comprimento total é retornado como o valor de retorno. Caso contrário, esta função retornará um valor de falha de -1.
Como kcp.rcv_queue
possui apenas o pacote [PSH sn=0 frg=2 len=1376]
neste momento, a tentativa de leitura falhou.
Se estiver no modo stream (kcp.stream != 0), todos os pacotes serão marcados como
seg.frg=0
. Neste momento, todos os pacotes na filakcp.rcv_queue
serão lidos com sucesso.
Chame ikcp_input: em k1 (Figura etapa 3-1)
Pacote de entrada [ACK sn=0 una=1]
.
UNA confirma :
Qualquer pacote recebido tentará primeiro a confirmação da UNA (ikcp.c:789)
Confirmou e removeu todos os pacotes na fila kcp.snd_buf
seg.sn
é menor que o valor una (ikcp:599) confirmando o valor seg.una
do pacote.
[PSH sn=0 frg=2 len=1376]
é confirmado e removido da fila kcp.snd_buf
de k1.
Confirmação ACK :
Analise o tipo de pacote de dados e entre no processamento da ramificação de confirmação. (ikcp.c:792)
Combine os números de sequência dos pacotes de confirmação e remova os pacotes correspondentes. (ikcp.c:581)
Ao realizar a confirmação ACK na etapa 3-1, a fila kcp.snd_buf
já está vazia porque o único pacote [PSH sn=0 frg=2 len=1376]
foi confirmado antecipadamente pela UNA.
Se kcp.snd_buf
forem confirmados (alterações kcp.snd_una
), o valor cwnd do tamanho da janela de congestionamento será recalculado e atualizado para 2 (ikcp.c:876).
Diagrama de confirmação UNA / ACK, este diagrama registra adicionalmente o status de kcp.snd_una
não marcado no diagrama de processo:
A confirmação ACK não funcionará para pacotes de confirmação que chegam sequencialmente. Para pacotes que chegam fora de ordem, o pacote é removido individualmente após confirmação via ACK:
Chame ikcp_flush em k1: (Figura etapa 3-2)
Assim como na etapa 1-2, o valor da nova janela de congestionamento kcp.cwnd
foi atualizado para 2, e os dois pacotes restantes serão enviados desta vez: [PSH sn=1 frg=1 len=1376]
[PSH sn=2 frg=0 len=1344]
.
Na etapa 3-3, kcp.output
será chamado duas vezes para enviar pacotes de dados respectivamente.
Chame ikcp_input: em k2 (Figura etapa 4-1)
Pacotes de entrada [PSH sn=1 frg=1 len=1376]
e [PSH sn=2 frg=0 len=1344]
.
Cada pacote é adicionado à fila kcp.rcv_buf
, fica disponível e, eventualmente, tudo é movido para a fila kcp.rcv_queue
.
Neste momento, o comprimento da fila kcp.rcv_queue
de k2 é 3 e kcp.snd_buf
é 0. O valor de kcp.rcv_nxt
para o próximo pacote esperado a ser recebido é atualizado de 1 para 3.
Chame ikcp_flush em k2: (Figura etapa 4-2)
As informações de confirmação em kcp.acklist
serão codificadas em pacotes [ACK sn=1 una=3]
e [ACK sn=2 una=3]
e enviadas na etapa 4-3.
Na verdade, esses dois pacotes serão gravados em um buffer e uma chamada kcp.output
será feita.
Chame ikcp_recv em k2: (Figura etapa 4-4)
Existem agora três pacotes não lidos em kcp.rcv_queue
: [PSH sn=0 frg=2 len=1376]
[PSH sn=1 frg=1 len=1376]
e [PSH sn=2 frg=0 len=1344]
Nesse momento, um pacote com valor seg.frg
igual a 0 é lido e o comprimento total legível é calculado como 4.096. Então todos os dados nos três pacotes serão lidos e gravados no buffer de leitura e o sucesso será retornado.
É necessário prestar atenção a outra situação : se a fila kcp.rcv_queue
contém 2 pacotes enviados pelo usuário com seg.frg
de 2/1/0/2/1/0 e está fragmentada em 6 pacotes de dados, o correspondente é também é necessário chamar ikcp_recv duas vezes para ler todos os dados completos recebidos.
Chame ikcp_input: em k1 (Figura etapa 5-1)
Insira pacotes de confirmação [ACK sn=1 una=3]
e [ACK sn=2 una=3]
e analise para seg.una
=3. O pacote [PSH sn=1 frg=1 len=1376]
[PSH sn=2 frg=0 len=1344]
é confirmado e removido da fila kcp.snd_buf
através de una.
Todos os dados enviados foram confirmados.
A janela é usada para controle de fluxo. Ele marca um intervalo lógico da fila. Devido ao processamento dos dados reais, a posição da fila continua a se mover para a posição alta do número de sequência. Logicamente, esta janela continuará a se mover, expandir e contrair ao mesmo tempo, por isso também é chamada de janela deslizante .
Este diagrama esquemático é outra representação das etapas 3-1 a 4-1 do diagrama de fluxo na seção "Processo básico de envio e recebimento de dados". Como operações fora do escopo das etapas, as direções dos dados são indicadas por setas semitransparentes.
Todos os dados são processados através da função apontada pela seta e movidos para um novo local (duas vezes o tamanho da imagem):
Os dados transmitidos pela função ikcp_send no final do envio serão armazenados diretamente na fila de envio kcp.snd_queue
após o processamento do fatiamento de dados.
toda vez que ikcp_flush é chamado. O tamanho da janela para esta transmissão será calculado com base no tamanho da janela de envio kcp.snd_wnd
, no tamanho da janela remota kcp.rmt_wnd
e no tamanho da janela de congestionamento kcp.cwnd
O valor é o mínimo dos três: min( kcp.snd_wnd
, kcp.rmt_wnd
, kcp.cwd
) (ikcp.c:1017).
Se o parâmetro nc for definido como 1 através da função ikcp_nodelay e o modo de controle de fluxo estiver desligado, o cálculo do valor da janela de congestionamento será ignorado. O resultado do cálculo da janela de envio é min( kcp.snd_wnd
, kcp.rmt_wnd
) (ikcp.c:1018).
Na configuração padrão de desligar apenas o modo de controle de fluxo, o número de pacotes de dados que podem ser enviados pela primeira vez é o valor de tamanho padrão de kcp.snd_wnd
32. Isso é diferente do exemplo básico do processo de envio e recebimento, no qual apenas um pacote pode ser enviado pela primeira vez porque o controle de fluxo está habilitado por padrão.
Os pacotes de dados recém-enviados serão movidos para a fila kcp.snd_buf
.
Para dados ikcp_send, há apenas um limite de fatia de 127 (ou seja, 127*
kcp.mss
=174752 bytes). Não há limite para o número total de pacotes na fila de envio. Consulte: Como evitar atrasos no acúmulo de cache
kcp.snd_buf
armazena dados que serão ou foram enviados.
Cada vez que ikcp_flush
é chamado, a janela de envio é calculada e o pacote de dados é movido de kcp.snd_queue
para a fila atual. Todos os pacotes de dados na fila atual serão processados em três situações:
1. Primeiro envio de dados (ikcp.c:1053)
O número de vezes que um pacote é enviado será registrado em seg.xmit
. O processamento do primeiro envio é relativamente simples e alguns parâmetros seg.rto
/ seg.resendts
para tempo limite de retransmissão serão inicializados.
2. Tempo limite de dados (ikcp.c:1058)
Quando o tempo kcp.current
registrado internamente excede o período de tempo limite seg.resendts
do próprio pacote, ocorre uma retransmissão de tempo limite.
3. Confirmação de cruzamento de dados (ikcp.c:1072)
Quando o final dos dados é estendido e o número de spans seg.fastack
excede a configuração de retransmissão de span kcp.fastresend
, ocorre a retransmissão de span. (o padrão kcp.fastresend
é 0 e, quando é 0, é calculado como UINT32_MAX e a retransmissão de span nunca ocorrerá.) Após uma retransmissão de tempo limite, o pacote atual seg.fastack
será redefinido para 0.
A lista de confirmação é uma lista de registros simples que originalmente registra números de sequência e carimbos de data/ seg.ts
( seg.sn
) na ordem em que os pacotes são recebidos.
Portanto, no diagrama esquemático deste artigo,
kcp.ack_list
não terá nenhuma posição de elemento em branco desenhada. Porque não é uma fila ordenada logicamente (da mesma forma, embora o pacote na filasnd_queue
ainda não tenha recebido um número de sequência, seu número de sequência lógico foi determinado).
A extremidade receptora armazena em buffer pacotes de dados que não podem ser processados temporariamente.
Todos os pacotes de dados recebidos de ikcp_input chegarão primeiro nesta fila e as informações serão registradas em kcp.ack_list
na ordem original de chegada.
Existem apenas duas situações em que os dados ainda permanecerão nesta fila:
Aqui, o pacote [PSH sn=0]
é recebido primeiro, que atende às condições dos pacotes disponíveis e é movido para kcp.rev_queue
.
Então o pacote [PSH sn=2]
foi recebido, que não era o próximo pacote esperado a ser recebido ( seg.sn
== kcp.rcv_nxt
), fazendo com que este pacote permanecesse em kcp.rcv_buf
.
Depois de receber o pacote [PSH sn=1]
, mova os dois pacotes presos [sn=1]
[sn=2]
para kcp.rcv_queue
.
kcp.rcv_queue
atinge o tamanho da janela de recebimento kcp.rcv_wnd
(ikcp_recv não foi chamado a tempo).A extremidade receptora armazena dados que podem ser lidos pela camada superior.
No modo streaming, todos os pacotes disponíveis são lidos; no modo não streaming, segmentos de dados fragmentados são lidos e montados em dados brutos completos.
Após a conclusão da leitura, será feita uma tentativa de mover os dados de kcp.rcv_buf
para esta fila (possivelmente para recuperar de um estado de janela de recebimento completo).
O valor da janela de envio kcp.snd_wnd
é um valor configurado e o padrão é 32.
A janela remota kcp.rmt_wnd
é um valor que será atualizado quando o remetente receber um pacote do destinatário (não apenas um pacote de confirmação). Ele registra o comprimento disponível (ikcp.c:1086) da fila de recebimento da extremidade receptora kcp.rcv_queue
quando o pacote de dados atual é enviado pela extremidade receptora. O valor inicial é 128.
A janela de congestionamento é um valor calculado que cresce algoritmicamente cada vez que os dados são recebidos via ikcp_input.
Se a perda de pacotes e a retransmissão rápida forem detectadas ao liberar os dados ikcp_flush, eles serão recalculados de acordo com o algoritmo.
A posição onde
kcp.cwnd
é inicializado como 1 está na primeira chamada de ikcp_update para ikcp_flush.
O valor kcp.rcv_wnd
da janela de recebimento é um valor configurado e o padrão é 128. Limita o comprimento máximo da fila de recebimento kcp.rcv_queue
.
Nesta seção, é fornecida uma versão aprimorada de kcp_optional.c baseada no código de exemplo na seção "Envio e recebimento de dados básicos". Você pode observar ainda mais o comportamento do protocolo modificando a definição da macro.
O código de amostra encerra o processo especificando a gravação de um número específico de dados de comprimento fixo em k1 e a leitura completa em k2.
Macros são fornecidas para controlar funções específicas:
A janela de congestionamento é registrada através dos valores de kcp.cwnd
e kcp.incr
. Como a unidade registrada por kcp.cwnd
é um pacote, é necessário kcp.incr
adicional para registrar a janela de congestionamento expressa em unidades de comprimento de bytes.
Assim como o TCP, o controle de congestionamento KCP também é dividido em dois estágios: início lento e prevenção de congestionamento:
A janela de congestionamento aumenta no processo de confirmação do pacote de dados, toda vez que kcp.snd_buf
são confirmados (confirmação UNA efetiva, alterações kcp.snd_una
). E quando a janela de congestionamento é menor que a janela remota registrada kcp.rmt_wnd
, a janela de congestionamento aumenta. (ikcp:875)
1. Se a janela de congestionamento for menor que o limite de início lento kcp.ssthresh
, ela está no estágio de início lento e o crescimento da janela de congestionamento é relativamente agressivo neste momento. A janela de congestionamento cresce em uma unidade.
2. Se a janela de congestionamento for maior ou igual ao limite de início lento, ela está no estágio de prevenção de congestionamento e o crescimento da janela de congestionamento é relativamente conservador. Se kcp.incr
aumentar mss/16 a cada vez, 16 confirmações válidas de UNA serão necessárias antes que uma janela de congestionamento de unidade seja aumentada. O crescimento real da janela da fase de prevenção de congestionamento é:
(mss * mss) / incr + (mss / 16)
Como incr=cwnd*mss é:
((mss * mss) / (cwnd * mss)) + (mss / 16)
Equivalente a:
(mss / cwnd) + (mss / 16)
A janela de congestionamento cresce incrementalmente para cada cwnd e cada 16 confirmações válidas do UNA.
Redução da janela de congestionamento : quando a função ikcp_flush detecta perda de pacotes em retransmissões ou tempos limite, a janela de congestionamento é reduzida.
1. Quando ocorre a retransmissão de intervalo, o limite de início lento kcp.ssthresh
é definido como metade do intervalo de número de sequência não reconhecido. O tamanho da janela de congestionamento é o limite de início lento mais o valor de configuração de retransmissão rápida kcp.resend
(ikcp:1117):
ssthresh = (snd_nxt - snd_una) / 2
cwnd = ssthresh + resend
2. Quando um tempo limite de perda de pacotes é detectado, o limite de início lento é definido como metade da janela de congestionamento atual. Janela de congestionamento definida como 1 (ikcp:1126):
ssthresh = cwnd / 2
cwnd = 1
Observe o início lento 1 : execute o código de exemplo com a configuração padrão e você observará que o processo de início lento é ignorado rapidamente. Isso ocorre porque o limite de início lento padrão é 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 no conteúdo de saída é o tempo lógico, n é o número de vezes que k1 envia dados no ciclo, o valor de cwnd=1|1 indica que o 1 na frente do símbolo da barra vertical é a janela calculada quando ikcp_flush, que é, min(kcp. no modo de controle de fluxo. snd_wnd, kcp.rmt_wnd, kcp.cwnd), o valor do seguinte 1 é kcp.cwnd
.
Observe graficamente o crescimento da janela de congestionamento sob a configuração padrão: quando na fase de prevenção de congestionamento, quanto maior a janela de congestionamento, mais suave será o crescimento da janela de congestionamento.
Observe o início lento 2 : Ajuste o valor inicial do limite de início lento da configuração de amostra KCP_THRESH_INIT para 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
...
Ao interceptar apenas um curto período antes da rodada de transmissão, pode-se observar que o início lento também aumenta de forma linear por padrão.
Observe a desativação do reconhecimento atrasado : envie o máximo de dados possível e desative a opção de envio atrasado ACK_DELAY_FLUSH e simule uma perda de pacote:
#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
...
Neste caso, obtém-se um gráfico clássico de partida lenta e prevenção de congestionamento. A perda de pacotes foi detectada na 15ª rodada de transmissão (t=1500):
Desativar a opção de envio atrasado significa que a parte receptora dos dados chamará ikcp_flush imediatamente após cada execução de ikcp_input para enviar de volta um pacote de confirmação.
O processo de início lento neste momento aumenta exponencialmente a cada RTT (Round-Trip Time, tempo de ida e volta), porque cada pacote de confirmação será enviado de forma independente, fazendo com que a janela de congestionamento do remetente cresça, e como a janela de congestionamento aumenta, o número de pacotes enviados em cada RTT aumentarão em dobro.
Se a confirmação atrasar, um pacote de confirmação será enviado junto. O processo de aumento da janela de congestionamento será executado apenas uma vez cada vez que a função ikcp_input for chamada, portanto, a fusão de vários pacotes de confirmação recebidos não terá o efeito de aumentar a janela de congestionamento várias vezes.
Se o intervalo do ciclo de clock for maior que o RTT, ele aumentará exponencialmente a cada intervalo. O diagrama esquemático mostra uma situação possível:
A premissa do crescimento exponencial é que os dados enviados da próxima vez podem satisfazer o dobro do número de pacotes de dados da última vez. Se os dados gravados no final do envio forem insuficientes, o crescimento exponencial não será alcançado.
Deve-se observar que mesmo que o código de amostra envie uma confirmação imediata, ele afeta apenas a forma como o destinatário envia a confirmação. O remetente também precisa aguardar o próximo ciclo antes de processar esses pacotes de confirmação. Portanto, o tempo aqui é apenas para referência, a menos que o pacote recebido não seja processado e armazenado imediatamente no código real do transceptor da rede, ele deverá aguardar até o ciclo de atualização antes de responder.
Com base nas características do KCP, deve haver uma forma mais direta de aumentar a janela de congestionamento e desligar diretamente o controle de fluxo para obter um envio mais agressivo:
#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
Você também pode modificar diretamente o valor da janela de congestionamento conforme necessário.
Observe que a janela remota está cheia : Se o comprimento dos dados enviados estiver próximo do tamanho padrão da janela remota e o terminal receptor não os ler a tempo, você encontrará um período em que nenhum dado poderá ser enviado (observe que no código de exemplo, o destinatário primeiro envia o pacote de confirmação e depois lê o conteúdo novamente):
#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
Quando o tamanho da janela remota kcp.rmt_wnd
registrado no receptor for 0, a fase de espera da sonda (espera da sonda, ikcp.c:973) será iniciada em ikcp_flush. kcp.ts_probe
registrará um tempo inicialmente de 7.000 milissegundos (gravado em kcp->probe_wait
).
Quando chegar a hora, codifique adicionalmente um pacote do tipo IKCP_CMD_WASK e envie-o para a extremidade receptora (ikcp.c:994) para solicitar que a extremidade remota envie de volta um pacote do tipo IKCP_CMD_WINS para atualizar kcp.rmt_wnd
Se o tamanho da janela remota for sempre 0, kcp->probe_wait
aumenta pela metade do valor atual a cada vez e, em seguida, atualiza o tempo de espera. O tempo máximo de espera é de 120.000 milissegundos (120 segundos).
Quando o tamanho da janela remota não é 0, o status de detecção acima é apagado.
Neste exemplo, não esperamos os 7 segundos iniciais antes de restaurar o tamanho da janela remota gravada. Porque na operação de ikcp_recv na extremidade receptora para leitura de dados, quando o comprimento da fila kcp.rcv_queue
é maior ou igual à janela de recebimento kcp.rcv_wnd
antes de ler os dados (a janela de leitura está cheia), um bit de sinalização (ikcp .c:431) e envie um pacote do tipo IKCP_CMD_WINS (ikcp.c:1005) na próxima vez ikcp_flush Para notificar o remetente para atualizar o tamanho da janela remota mais recente.
Para evitar este problema, os dados precisam ser lidos na extremidade receptora em tempo hábil. No entanto, mesmo que a janela remota se torne menor, a janela de envio do remetente ficará menor, resultando em atrasos adicionais. Ao mesmo tempo, também é necessário aumentar a janela de recebimento do receptor.
Tente modificar o valor RECV_TIME para um valor relativamente grande (por exemplo, 300 segundos) e observe o envio do pacote IKCP_CMD_WASK.
Conforme descrito na descrição da fila kcp.snd_buf
, ao chamar ikcp_flush, todos os pacotes da fila serão atravessados, caso o pacote não seja enviado pela primeira vez. Em seguida, ele verificará se o pacote foi cruzado com reconhecimento o número especificado de vezes ou se o período de tempo limite foi atingido.
Os campos relacionados ao tempo de ida e volta e ao cálculo do tempo limite são:
kcp.rx_rttval
: tempo suave de jitter da redekcp.rx_srtt
: tempo de ida e volta suavekcp.rx_rto
(Tempo limite de retransmissão de recebimento): tempo limite de retransmissão, valor inicial 200kcp.rx_minrto
: Tempo limite mínimo de retransmissão, valor inicial 100kcp.xmit
: contagem global de retransmissõesseg.resendts
: carimbo de data/hora de retransmissãoseg.rto
: tempo limite de retransmissãoseg.xmit
: contagem de retransmissõesAntes de discutir como o pacote calcula os tempos limite, vamos primeiro ver como os campos relacionados de tempo de ida e volta e tempo limite são calculados:
Registro de tempo de ida e volta : Cada vez que um pacote de confirmação ACK é processado, o pacote de confirmação carregará o número de sequência e a hora em que o número de sequência foi enviado ao remetente ( seg.sn
/ seg.ts
). o processo de atualização de horário é executado.
O valor da RTT é o tempo de ida e volta de um único pacote, ou seja, rtt = kcp.current
- seg.ts
Se o tempo de ida e volta suavizado kcp.rx_srtt
for 0, significa que a inicialização é realizada: kcp.rx_srtt
é diretamente registrada como RTT kcp.rx_rttval
for registrada como metade da RTT.
No processo de não iniciação, é calculado um valor delta, que representa o valor de flutuação deste RTT e o kcp.rx_srtt
registrado (ikcp.c: 550):
delta = abs(rtt - rx_srtt)
O novo kcp.rx_rttval
é atualizado pelo valor ponderado do antigo kcp.rx_rttval
e delta:
rx_rttval = (3 * rx_rttval + delta) / 4
O novo kcp.rx_srtt
é atualizado pelo valor ponderado do antigo kcp.rx_srtt
e rtt e não pode ser menor que 1:
rx_srtt = (7 * rx_srtt + rtt) / 8
rx_srtt = max(1, rx_srtt)
O novo rx_rto
é atualizado pelo valor mínimo do tempo de ida e volta suavizado kcp.rx_srtt
mais o ciclo do relógio kcp.interval
e 4 vezes rx_rttval
, e o intervalo é limitado a [ kcp.rx_minrto
, 60000]:
rto = rx_srtt + max(interval, 4 * rttval)
rx_rto = min(max(`kcp.rx_minrto`, rto), 60000)
Idealmente, quando a rede tiver apenas um atraso fixo e sem jitter, o valor do kcp.rx_rttval
se aproximará de 0, e o valor do kcp.rx_rto
é determinado pelo tempo de viagem e um ciclo de relógio de ida e volta.
Diagrama de cálculo do tempo de ida e volta suave:
Tempo de entrega do primeiro contrato (ikcp.c: 1052):
seg.rto
gravará o status kcp.rx_rto
, e o primeiro horário de tempo limite do pacote de dados é seg.rto
+ rtomin milissegunds.
O RTOMIN é calculado pelo kcp.rx_rto
, se o modo Nodelay estiver ativado. rtomin é 0, caso contrário, kcp.rx_rto
/8.
Tempo limite para Nodelay não ativado:
resendts = current + rx_rto + rx_rto / 8
Ativar tempo de tempo nodelay:
resendts = current + rx_rto
Retransmissão de tempo limite (ikcp.c: 1058):
Quando o tempo interno atinge o tempo de tempo limite seg.resendts
do pacote de dados, o pacote com esse número de sequência é retransmitido.
Quando o modo Nodelay não está ativado, o incremento do seg.rto
é max ( seg.rto
, kcp.rx_rto
) (crescimento duplo):
rto += max(rto, rx_rto)
Quando o Nodelay estiver ativado e o Nodelay for 1, aumente seg.rto
pela metade de cada vez (1,5 vezes aumenta):
rto += rto / 2
Quando o Nodelay está ativado e o Nodelay é 2, kcp.rx_rto
é aumentado pela metade de cada vez (1,5 vezes aumenta):
rto += rx_rto / 2
O novo timeout é após seg.rto
milissegundos:
resendts = current + rx_rto
Retransmissão ao longo do tempo (ikcp.c: 1072):
Quando um pacote de dados é cruzado um número especificado de vezes, uma retransmissão de cruzamento é acionada.
seg.rto
não é atualizado ao retransmitir o tempo e o próximo tempo de retransmissão do tempo limite é recalculado diretamente:
resendts = current + rto
Observe o tempo limite padrão
Envie apenas um pacote, solte -o quatro vezes e observe o tempo limite e o tempo de retransmissão.
Para a configuração padrão, o valor inicial do kcp.rx_rto
é de 200 milissegundos e o primeiro tempo de tempo limite é de 225 milissegundos. .
#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
Observe um aumento de 1,5x na RTO com base no próprio pacote
#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
Assista ao crescimento de 1.5x com base no 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
Observe retransmissões de span
Os pacotes [sn=0]
[sn=1]
processados pela mesclagem não acionam retransmissão [sn=2]
extensão. No final, foi retransmitido através do tempo limite.
#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
Ao enviar pacotes de dados em duas etapas, o segundo grupo de pacotes será cruzado duas vezes ao executar a confirmação do IKCP_Input e [sn=0]
será acumulado e retransmitido durante o próximo 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
O artigo é licenciado sob uma Licença Internacional Creative Commons Attribution-NonCommercial-Noderivs 4.0.
O código no projeto é de código aberto usando a licença do MIT.
Sobre a fonte da imagem: noto sem sc