특정 애플리케이션에서는 단순히 TCP를 사용하는 것만으로는 요구 사항을 충족할 수 없습니다. UDP 데이터그램을 직접 사용하는 것은 데이터 신뢰성을 보장할 수 없으며, 애플리케이션 계층에서 UDP 기반의 안정적인 전송 프로토콜을 구현해야 하는 경우가 많습니다.
KCP 프로토콜을 직접 사용하는 것은 강력한 자동 재전송 프로토콜을 구현하고 그 위에 자유로운 매개변수 조정을 제공하는 옵션입니다. 구성 매개변수와 적절한 호출 방법을 통해 다양한 시나리오의 요구 사항에 적응합니다.
KCP 소개:
KCP는 TCP보다 10~20% 더 많은 대역폭을 사용하여 평균 지연을 30~40%, 최대 지연을 3배 줄일 수 있는 빠르고 안정적인 프로토콜입니다. 순수 알고리즘 구현은 기본 프로토콜(예: 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 감지를 담당하지 않습니다. 기본값은 ikcp_setmtu를 사용하여 이 값을 설정할 수 있습니다. 이 값은 데이터 패킷을 결합하고 조각화할 때 최대 전송 단위에 영향을 미칩니다. MTU가 작을수록 라우팅 우선순위에 영향을 미칩니다.
이 글에서는 기본적으로 KCP를 실행할 수 있는 kcp_basic.c 코드를 제공합니다. 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
: 세그먼트 번호ikcp_create 함수를 통해 KCP 컨텍스트 구조 IKCPCB를 생성합니다.
IKCPCB는 외부에서 ikcp_send(발신자에 대한 사용자 입력) 및 ikcp_input 입력 데이터(수신기에 대한 발신자 입력)를 호출하여 데이터 및 상태를 저장하는 해당 IKCPSEG 구조를 내부적으로 생성합니다.
또한 IKCPSEG 구조는 ikcp_recv(수신단에서 사용자에 의해 제거됨) 및 ikcp_input 확인 데이터(수신단에서 송신단이 수신함)를 통해 제거됩니다.
자세한 데이터 흐름 방향은 대기열 및 창 섹션을 참조하세요.
IKCPSEG의 생성 및 소멸은 주로 위의 네 가지 상황에서 발생하며 다른 상황은 내부 대기열과 기타 최적화 간의 이동에서 일반적입니다.
다음 문서에서는 표시되는 모든 IKCPCB 및 IKCPSEG 구조 필드가
标记
로 강조 표시됩니다(마크다운 브라우징만 해당, 다른 사용자는 볼 수 없음). IKCPCB 구조의 모든 필드에는kcp.
접두사가 붙고 IKCPSEG 구조의 모든 필드에는seg.
접두사가 붙습니다. 일반적으로 소스 코드에서 해당 변수 이름이나 함수 매개변수 이름도kcp
또는seg
입니다.
이 코드는 단순히 지정된 길이의 데이터를 k1이라는 KCP 객체에 쓰고 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 개체의 출력 데이터 동작을 정의하는 데 사용되는 kcp.output
외에도 kcp_user_output 함수에 바인딩됩니다. kcp.writelog
인쇄 디버깅을 위해 kcp_user_writelog 함수에도 바인딩됩니다.
또한 kcp.output
콜백은 다른 ikcp_input을 재귀적으로 호출할 수 없으므로(결국 자신의 kcp.output
으로 재귀하므로) 모든 출력 데이터를 중간 위치에 저장한 다음 kcp.output
을 종료한 후 k2에 입력해야 합니다. 기능. 이것이 샘플 코드에 정의된 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 코드에 내장되어 인쇄된 디버깅 정보입니다. kcp_user_writelog를 통해 구별로 k1/k2 행 접두사가 추가됩니다.
이 코드의 전송 확인 프로세스에 대한 전체 도식 다이어그램은 다음과 같이 설명됩니다(크기의 두 배).
k1에서 ikcp_send를 호출합니다. (그림 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
의 최대 메시지 길이는 kcp.mtu
에서 프로토콜 오버헤드(24바이트)를 빼서 계산됩니다. 기본값은 1376입니다.
이번에는 kcp.output
콜백이 실행되지 않으며 모든 샤드 데이터가 할당되어 IKCPSEG 구조에 기록되고 kcp.snd_queue
대기열(ikcp.c:528)에 추가됩니다.
이때 k1의 kcp.snd_queue
큐 길이는 3이고, kcp.snd_buf
큐 길이는 0입니다.
k1에서 ikcp_flush를 호출합니다. (그림 1-2단계)
여기서는 윈도우의 구체적인 계산 과정은 무시됩니다. k1이 처음으로 ikcp_flush를 호출할 때 정체 윈도우
kcp.cwnd
의 값이 1이라는 것만 알면 됩니다.
혼잡 창 제한으로 인해 처음에는 하나의 패킷만 보낼 수 있습니다. kcp.snd_queue
큐의 첫 번째 데이터 길이를 갖는 IKCPSEG 객체는 kcp.snd_buf
큐(ikcp.c:1028)로 이동되고, kcp.snd_nxt
에 따라 할당된 시퀀스 번호 seg.sn
의 값은 0(ikcp .c:1036) , seg.cmd
필드는 IKCP_CMD_PUSH입니다. 데이터 밀어넣기 패키지를 나타냅니다.
이때 k1의 kcp.snd_queue
큐 길이는 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에 정의됨
k2에서 ikcp_input을 호출합니다. (그림 2-1단계)
데이터 패킷 [PSH sn=0 frg=2 len=1376]
을 입력하고, 패킷 헤더를 구문 분석하여 유효성을 확인합니다. (ikcp.c:769)
데이터 패킷 유형을 분석하고 데이터 푸시 분기 처리를 시작합니다. (ikcp.c:822)
확인 목록 kcp.acklist
(ikcp.c:828)에 데이터 패킷의 seq.sn
값과 seq.ts
값을 기록합니다. 참고 : 이 예에서 seq.ts
값은 항상 0입니다.
수신된 패킷을 kcp.rcv_buf
대기열에 추가합니다. (ikcp:709)
kcp.rcv_buf
대기열의 첫 번째 데이터 패킷이 사용 가능한지 확인합니다. 사용 가능한 데이터 패킷인 경우 kcp.rcv_queue
대기열로 이동합니다. (ikcp.c:726)
kcp.rcv_buf
에서 사용 가능한 데이터 패킷은 수신될 것으로 예상되는 다음 데이터 시퀀스 번호( kcp.rcv_nxt
에서 가져옴, 다음 데이터 시퀀스 번호는 seg.sn
== 0이어야 함) 및 kcp.rcv_queue
대기열이 수신된 창 크기보다 작습니다.
이 단계에서는 kcp.rcv_buf
대기열의 유일한 데이터 패킷이 kcp.rcv_queue
대기열로 직접 이동됩니다.
이때 k2의 kcp.>rcv_queue
큐 길이는 1이고, kcp.snd_buf
큐 길이는 0이다. 다음에 수신되는 데이터 시퀀스 번호 kcp.rcv_nxt
의 값이 0에서 1로 업데이트됩니다.
k2에서 ikcp_flush를 호출합니다. (그림 2-2단계)
k2의 첫 번째 ikcp_flush 호출에서. 확인 목록 kcp.acklist
에 데이터가 있으므로 확인 패킷이 인코딩되어 전송됩니다(ikcp.c:958).
확인 패킷의 seg.una
값에는 kcp.rcv_nxt
=1이 할당됩니다.
이 패킷은 [ACK sn=0 una=1]
로 기록됩니다. 이는 ack 확인에서 패킷 시퀀스 번호 0이 확인되었음을 의미합니다. una 확인에서는 패킷 번호 1 이전의 모든 패킷이 확인됩니다.
2-3단계에서는 kcp.output
호출하여 데이터 패킷을 보냅니다.
k2에서 ikcp_recv를 호출합니다. (그림 2-4단계)
kcp.rcv_queue
대기열에 seg.frp
값이 0(ikcp.c:459)인 패킷이 포함되어 있는지 확인합니다. 이 패킷이 포함되어 있으면 seg.frp
첫 번째 패킷과 이전 패킷의 데이터를 기록합니다. 이 패킷의 전체 길이가 반환 값으로 반환됩니다. 그렇지 않은 경우 이 함수는 실패 값 -1을 반환합니다.
kcp.rcv_queue
에는 현재 [PSH sn=0 frg=2 len=1376]
패키지만 있으므로 읽기 시도가 실패했습니다.
스트림 모드(kcp.stream != 0)인 경우 모든 패킷은
seg.frg=0
으로 표시됩니다. 이때kcp.rcv_queue
대기열의 모든 패킷은 성공적으로 읽혀집니다.
k1에서 ikcp_input을 호출합니다(그림 3-1 단계).
입력 패킷 [ACK sn=0 una=1]
.
UNA는 다음을 확인합니다 .
수신된 모든 패키지는 먼저 UNA 확인을 시도합니다(ikcp.c:789).
kcp.snd_buf
큐에서 패킷의 seg.sn
seg.una
이 una 값(ikcp:599)보다 작은 모든 패킷을 확인하고 제거했습니다.
[PSH sn=0 frg=2 len=1376]
이 확인되어 k1의 kcp.snd_buf
대기열에서 제거됩니다.
ACK 확인 :
데이터 패킷의 유형을 분석하고 확인 분기 처리에 들어갑니다. (ikcp.c:792)
확인 패킷의 시퀀스 번호를 일치시키고 해당 패킷을 제거합니다. (ikcp.c:581)
3-1 단계에서 ACK 확인을 수행할 때 UNA가 미리 확인한 패킷 [PSH sn=0 frg=2 len=1376]
만 패킷이므로 kcp.snd_buf
대기열은 이미 비어 있습니다.
kcp.snd_buf
대기열 헤더 데이터가 확인되면( kcp.snd_una
변경) 혼잡 창 크기 cwnd 값이 다시 계산되어 2(ikcp.c:876)로 업데이트됩니다.
UNA/ACK 확인 다이어그램, 이 다이어그램은 프로세스 다이어그램에서 표시되지 않은 kcp.snd_una
의 상태를 추가로 기록합니다.
ACK 승인은 순차적으로 도착하는 승인 패킷에 대해서는 작동하지 않습니다. 순서에 맞지 않게 도착하는 패킷의 경우 ACK를 통해 확인된 후 패킷이 개별적으로 제거됩니다.
k1에서 ikcp_flush를 호출합니다. (그림 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
대기열로 이동됩니다.
이때 k2의 kcp.rcv_queue
큐 길이는 3이고, kcp.snd_buf
길이는 0이다. 수신이 예상되는 다음 패킷에 대한 kcp.rcv_nxt
값이 1에서 3으로 업데이트됩니다.
k2에서 ikcp_flush를 호출합니다. (그림 4-2단계)
kcp.acklist
의 확인 정보는 [ACK sn=1 una=3]
및 [ACK sn=2 una=3]
패킷으로 인코딩되어 4-3단계에서 전송됩니다.
실제로 이 두 패킷은 버퍼에 기록되고 kcp.output
호출이 수행됩니다.
k2에서 ikcp_recv를 호출합니다. (그림 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
대기열에 seg.frg
값이 2/1/0/2/1/0인 2개의 사용자 전송 패킷이 포함되어 있고 6개의 데이터 패킷으로 조각화되어 있는 경우 해당하는 것은 다음과 같습니다. 또한 수신된 전체 데이터를 모두 읽으려면 ikcp_recv를 두 번 호출해야 합니다.
k1에서 ikcp_input을 호출합니다(그림 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]
패키지가 확인되고 una를 통해 kcp.snd_buf
대기열에서 제거됩니다.
전송된 모든 데이터가 확인되었습니다.
윈도우는 흐름 제어에 사용됩니다. 이는 대기열의 논리적 범위를 표시합니다. 실제 데이터 처리로 인해 큐의 위치는 계속해서 시퀀스 번호의 높은 위치로 이동합니다. 논리적으로 이 창은 계속 이동하고 확장하고 축소하는 동시에 계속 진행되므로 슬라이딩 창 이라고도 합니다.
이 개략도는 "기본 데이터 전송 및 수신 프로세스" 섹션에 있는 흐름도의 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).
ikcp_nodelay 함수를 통해 nc 매개변수를 1로 설정하고 흐름 제어 모드를 끄면 혼잡 창 값 계산이 무시됩니다. 송신 창의 계산 결과는 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로 계산되어 SPAN 재전송이 발생하지 않습니다.) 타임아웃 재전송 후 현재 패킷 seg.fastack
0으로 재설정됩니다.
승인 목록은 원래 패킷이 수신된 순서대로 시퀀스 번호와 타임스탬프( seg.sn
/ seg.ts
)를 기록하는 간단한 레코드 목록입니다.
따라서 이 기사의 회로도에서
kcp.ack_list
에는 빈 요소 위치가 그려지지 않습니다. 논리적으로 정렬된 대기열이 아니기 때문입니다(마찬가지로snd_queue
대기열의 패킷에 아직 시퀀스 번호가 할당되지 않았지만 해당 논리적 시퀀스 번호가 결정되었습니다).
수신측에서는 일시적으로 처리할 수 없는 데이터 패킷을 버퍼링합니다.
ikcp_input에서 들어오는 모든 데이터 패킷은 이 대기열에 먼저 도착하고 정보는 원래 도착 순서대로 kcp.ack_list
에 기록됩니다.
데이터가 이 대기열에 계속 남아 있는 상황은 두 가지뿐입니다.
여기서는 사용 가능한 패킷의 조건을 만족하는 패킷 [PSH sn=0]
을 먼저 수신하여 kcp.rev_queue
로 이동합니다.
그런 다음 수신될 것으로 예상되는 다음 패킷( seg.sn
== kcp.rcv_nxt
)이 아닌 [PSH sn=2]
패킷이 수신되어 이 패킷이 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
송신자가 수신자로부터 패킷(단지 승인 패킷이 아님)을 수신할 때 업데이트되는 값입니다. 현재 데이터 패킷이 수신단에서 전송될 때 수신단의 수신 큐 kcp.rcv_queue
의 사용 가능한 길이(ikcp.c:1086)를 기록합니다. 초기값은 128입니다.
혼잡 윈도우는 ikcp_input을 통해 데이터가 수신될 때마다 알고리즘적으로 증가하는 계산된 값입니다.
데이터 ikcp_flush를 플러시할 때 패킷 손실 및 빠른 재전송이 감지되면 알고리즘에 따라 다시 계산됩니다.
kcp.cwnd
가 1로 초기화되는 위치는 ikcp_flush에 대한 첫 번째 ikcp_update 호출에 있습니다.
수신 창 kcp.rcv_wnd
값은 구성된 값이며 기본값은 128입니다. 수신 큐 kcp.rcv_queue
의 최대 길이를 제한합니다.
이 섹션에서는 "기본 데이터 전송 및 수신" 섹션의 샘플 코드를 기반으로 하는 kcp_ional.c의 개선된 버전이 제공됩니다. 매크로 정의를 수정하여 프로토콜 동작을 더 자세히 관찰할 수 있습니다.
샘플 코드는 지정된 개수의 고정 길이 데이터를 k1에 쓰고 k2에서 완전히 읽도록 지정하여 프로세스를 종료합니다.
지정된 기능을 제어하기 위해 매크로가 제공됩니다.
혼잡 윈도우는 kcp.cwnd
및 kcp.incr
값을 통해 기록됩니다. kcp.cwnd
에 기록되는 단위는 패킷이므로 바이트 길이 단위로 표현되는 혼잡 윈도우를 기록하려면 추가 kcp.incr
이 필요합니다.
TCP와 마찬가지로 KCP 혼잡 제어도 느린 시작과 혼잡 회피의 두 단계로 나뉩니다.
kcp.snd_buf
대기열 헤더 데이터가 확인될 때마다 데이터 패킷을 확인하는 과정에서 정체 창이 커집니다 (유효 UNA 확인, kcp.snd_una
변경). 그리고 혼잡 윈도우가 기록된 원격 윈도우 kcp.rmt_wnd
보다 작을 경우 혼잡 윈도우가 증가합니다. (ikcp: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 시 계산된 창임을 나타냅니다. min(kcp. 흐름 제어 모드의 경우. snd_wnd, kcp.rmt_wnd, kcp.cwnd)이고, 다음 1의 값은 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_input을 실행할 때마다 즉시 ikcp_flush를 호출하여 확인 패킷을 다시 보냅니다.
이때 느린 시작 프로세스는 RTT (Round-Trip Time, 왕복 시간)마다 기하급수적으로 증가하는데, 이는 각 승인 패킷이 독립적으로 전송되기 때문에 송신자의 혼잡 윈도우가 커지게 되고, 혼잡 윈도우가 커지기 때문에 패킷 수가 늘어나게 되기 때문입니다. 각 RTT 내에서 전송되는 금액은 두 배로 늘어납니다.
확인이 지연될 경우 확인패키지가 함께 발송됩니다. 혼잡 창을 늘리는 프로세스는 ikcp_input 함수가 호출될 때마다 한 번만 실행되므로 수신된 여러 승인 패킷을 병합해도 혼잡 창을 여러 번 늘리는 효과는 없습니다.
클록 주기 간격이 RTT보다 크면 간격마다 기하급수적으로 증가합니다. 개략도는 가능한 상황을 보여줍니다.
기하급수적 증가의 전제는 다음 번에 전송되는 데이터가 이전 데이터 패킷 수의 두 배를 충족할 수 있다는 것입니다. 송신 측에 기록된 데이터가 부족하면 기하급수적 증가가 달성되지 않습니다.
샘플 코드가 즉시 확인을 보내더라도 수신 측에서 확인을 보내는 방식에만 영향을 미친다는 점에 유의해야 합니다. 또한 보낸 사람은 이러한 확인 패킷을 처리하기 전에 다음 주기를 기다려야 합니다. 따라서, 여기서의 시간은 단지 참고용일 뿐입니다. 수신된 패킷이 실제 네트워크 트랜시버 코드에 즉시 처리되어 저장되지 않는 한, 응답하기 전에 업데이트 주기까지 기다려야 합니다.
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_flush에서 프로브 대기 단계(probe wait, ikcp.c:973)가 시작됩니다. kcp.ts_probe
처음에 7000밀리초의 시간을 기록합니다( kcp->probe_wait
에 기록됨).
시간이 되면 추가로 IKCP_CMD_WASK 유형의 패킷을 인코딩하여 수신단(ikcp.c:994)으로 전송하여 원격 측에 IKCP_CMD_WINS 유형의 패킷을 다시 보내 kcp.rmt_wnd
업데이트하도록 요청합니다.
원격 창 크기가 항상 0인 경우 kcp->probe_wait
매번 현재 값의 절반씩 증가한 후 대기 시간을 업데이트합니다. 최대 대기 시간은 120000밀리초(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
: 최소 재전송 시간 초과, 초기값 100kcp.xmit
: 전역 재전송 횟수seg.resendts
: 재전송 타임스탬프seg.rto
: 재전송 시간 초과seg.xmit
: 재전송 횟수패키지가 시간 초과를 계산하는 방법을 논의하기 전에 먼저 왕복 시간 및 시간 초과 관련 필드가 계산되는 방법을 살펴보겠습니다.
왕복 시간 기록 : ACK 확인 패킷이 처리될 때마다 확인 패킷에는 시퀀스 번호와 해당 시퀀스 번호가 보낸 사람에게 전송된 시간( seg.sn
/ seg.ts
)이 합법적인 경우 왕복 여행이 포함됩니다. 시간 업데이트 프로세스가 실행됩니다.
rtt 값은 단일 패킷의 왕복 시간, 즉 rtt= kcp.current
- seg.ts
입니다.
원활한 왕복 kcp.rx_srtt
kcp.rx_srtt
가 kcp.rx_rttval
인 경우 초기화가 수행되었음을 의미합니다.
비 이니셜 화 프로세스에서, 델타 값이 계산되는데, 이는이 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은 Nodelay 모드가 활성화 된 경우 kcp.rx_rto
에 의해 계산됩니다. 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
밀리 초 후입니다.
resendts = current + rx_rto
시간에 따른 재전송 (IKCP.C : 1072) :
데이터 패킷이 지정된 횟수로 교차되면 교차 재전송 수가 트리거됩니다.
seg.rto
시간이 지남에 따라 재전송 할 때 업데이트되지 않으며 다음 타임 아웃 재전송 시간이 직접 다시 계산됩니다.
resendts = current + rto
기본 시간 초과를 관찰하십시오
하나의 패킷 만 보내고 4 번만 떨어 뜨린 후 시간 초과 및 재전송 시간을 관찰하십시오.
기본 구성의 경우 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
RTO를 기준으로 1.5 배의 성장을 시청하십시오
#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
스팬 레트라스 양식을 관찰하십시오
병합에 의해 처리 된 승인 패킷은 패킷 [sn=0]
및 패킷 [sn=1]
[sn=2]
의 승인을 트리거하여 하나의 범위 만 발생합니다. 결국, 그것은 타임 아웃을 통해 재전송되었습니다.
#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 확인을 수행 할 때 두 번째 패킷 그룹이 두 번 교차되며 다음 IKCP_FLUSH 중에 [sn=0]
축적되고 재전송됩니다.
#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 International License에 따라 라이센스가 부여됩니다.
프로젝트의 코드는 MIT 라이센스를 사용하는 오픈 소스입니다.
이미지 글꼴에 대해 : Noto Sans Sc