Dalam aplikasi tertentu, penggunaan TCP saja tidak dapat memenuhi kebutuhan. Penggunaan langsung datagram UDP tidak dapat menjamin keandalan data, dan sering kali diperlukan penerapan protokol transmisi yang andal berdasarkan UDP pada lapisan aplikasi.
Menggunakan protokol KCP secara langsung adalah sebuah opsi, yang mengimplementasikan protokol transmisi ulang otomatis yang kuat dan menyediakan penyesuaian parameter gratis di atasnya. Beradaptasi dengan kebutuhan skenario yang berbeda melalui parameter konfigurasi dan metode panggilan yang sesuai.
Pengantar KCP:
KCP adalah protokol yang cepat dan andal yang dapat mengurangi penundaan rata-rata sebesar 30%-40% dan mengurangi penundaan maksimum sebanyak tiga kali lipat dengan biaya bandwidth 10%-20% lebih banyak daripada TCP. Implementasi algoritma murni tidak bertanggung jawab atas pengiriman dan penerimaan protokol yang mendasarinya (seperti UDP). Pengguna perlu menentukan metode pengiriman paket data lapisan bawah dan memberikannya ke KCP dalam bentuk panggilan balik. Bahkan jam perlu diteruskan secara eksternal, dan tidak akan ada panggilan sistem apa pun secara internal. Seluruh protokol hanya memiliki dua file sumber, ikcp.h dan ikcp.c, yang dapat dengan mudah diintegrasikan ke dalam tumpukan protokol milik pengguna. Mungkin Anda telah mengimplementasikan protokol berbasis P2P atau UDP tetapi tidak memiliki implementasi protokol ARQ yang lengkap dan andal. Kemudian cukup salin kedua file ini ke proyek yang ada, tulis beberapa baris kode, dan Anda dapat menggunakannya.
Artikel ini secara singkat memperkenalkan proses dasar pengiriman dan penerimaan, jendela kemacetan, dan algoritma batas waktu protokol KCP, dan juga menyediakan kode contoh referensi.
Versi KCP yang dirujuk adalah versi terbaru pada saat penulisan. Artikel ini tidak akan sepenuhnya menempelkan semua kode sumber KCP, tetapi akan menambahkan tautan ke lokasi kode sumber yang sesuai pada titik-titik penting.
Struktur IKCPSEG digunakan untuk menyimpan status segmen data yang dikirim dan diterima.
Deskripsi semua bidang 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];
};
Bidang data
di akhir struktur digunakan untuk mengindeks data di akhir struktur. Memori tambahan yang dialokasikan akan memperpanjang panjang sebenarnya larik bidang data saat runtime (ikcp.c:173).
Struktur IKCPSEG hanya bersifat memori, dan hanya beberapa bidang yang dikodekan ke dalam protokol transport.
Fungsi ikcp_encode_seg mengkodekan header protokol transport:
/* 协议头一共 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;
}
Struktur IKCPCB menyimpan seluruh konteks protokol KCP, dan komunikasi protokol dilakukan dengan membuat dua objek IKCPCB di ujung yang berlawanan.
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;
Hanya ada 2 struktur antrian di KCP:
IQUEUEHEAD adalah daftar tertaut ganda sederhana yang menunjuk ke elemen awal (sebelumnya) dan terakhir (berikutnya) dari antrian:
struct IQUEUEHEAD {
/*
next:
作为队列时: 队列的首元素 (head)
作为元素时: 当前元素所在队列的下一个节点
prev:
作为队列时: 队列的末元素 (last)
作为元素时: 当前元素所在队列的前一个节点
*/
struct IQUEUEHEAD *next, *prev;
};
typedef struct IQUEUEHEAD iqueue_head;
Ketika antrian kosong, next/prev akan menunjuk ke antrian itu sendiri, bukan NULL.
Header struktur IKCPSEG sebagai elemen antrian juga menggunakan kembali struktur IQUEUEHEAD:
struct IKCPSEG
{
struct IQUEUEHEAD node;
/* ... */
}
Ketika digunakan sebagai elemen antrian, elemen sebelumnya (sebelumnya) dan elemen berikutnya (berikutnya) dalam antrian dimana elemen saat ini berada dicatat.
Ketika prev menunjuk ke antrian, berarti elemen saat ini berada di awal antrian, dan ketika next menunjuk ke antrian, berarti elemen saat ini berada di akhir antrian.
Semua operasi antrian disediakan sebagai makro.
Metode konfigurasi yang disediakan oleh KCP adalah:
Pilihan mode kerja :
int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc)
Opsi jendela maksimum :
int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
Ukuran jendela pengirim sndwnd
harus lebih besar dari 0, dan ukuran jendela penerima rcvwnd
harus lebih besar dari 128. Satuannya adalah paket, bukan byte.
Unit transmisi maksimum :
KCP tidak bertanggung jawab untuk mendeteksi MTU. Nilai defaultnya adalah 1400 byte. Nilai ini akan mempengaruhi unit transmisi maksimum saat menggabungkan dan memecah paket data. MTU yang lebih kecil akan mempengaruhi prioritas routing.
Artikel ini memberikan kode kcp_basic.c yang pada dasarnya dapat menjalankan KCP. Kode contoh yang kurang dari 100 baris adalah panggilan algoritma murni ke KCP dan tidak menyertakan penjadwalan jaringan apa pun. ( Penting : ikuti artikel untuk men-debugnya dan cobalah!)
Anda dapat menggunakannya untuk mendapatkan pemahaman awal tentang bidang struktur dasar pada struktur IKCPCB:
kcp.snd_queue
: kirim antrian (panjang catatan kcp.nsnd_que
)kcp.snd_buf
: kirim buffer (panjang catatan kcp.nsnd_buf
)kcp.rcv_queue
: antrian penerimaan (panjang catatan kcp.nrcv_que
)kcp.rcv_buf
: menerima buffer (panjang catatan kcp.nrcv_buf
)kcp.mtu
: unit transmisi maksimumkcp.mss
: Panjang pesan maksimumDan dalam struktur IKCPSEG:
seg.sn
: nomor seriseg.frg
: Nomor segmenBuat struktur konteks KCP IKCPCB melalui fungsi ikcp_create.
IKCPCB secara internal membuat struktur IKCPSEG yang sesuai untuk menyimpan data dan status dengan memanggil secara eksternal ikcp_send (input pengguna ke pengirim) dan ikcp_input input data (input pengirim ke penerima).
Selain itu, struktur IKCPSEG akan dihapus melalui ikcp_recv (dihapus oleh pengguna dari pihak penerima) dan data konfirmasi ikcp_input (diterima oleh pihak pengirim dari pihak penerima).
Untuk mengetahui arah aliran data secara detail, lihat bagian Antrean dan Jendela .
Pembuatan dan penghancuran IKCPSEG terutama terjadi pada empat situasi di atas, dan situasi lainnya umum terjadi pada pergerakan antara antrean internal dan optimalisasi lainnya.
Pada artikel berikut, semua bidang struktur IKCPCB dan IKCPSEG yang muncul akan disorot oleh
标记
(hanya penelusuran penurunan harga, orang lain mungkin tidak melihatnya). Semua bidang struktur IKCPCB akan diawali dengankcp.
, dan semua bidang struktur IKCPSEG akan diawali denganseg.
. Biasanya nama variabel atau nama parameter fungsi yang sesuai dalam kode sumber jugakcp
atauseg
.
Kode ini hanya menulis data dengan panjang tertentu ke objek KCP bernama k1 dan membaca data dari objek k2. KCP dikonfigurasi dalam mode default.
Proses dasarnya dapat dijelaskan secara sederhana melalui pseudocode sebagai:
/* 创建两个 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)
Dalam kode contoh, objek KCP yang dibuat terikat ke fungsi kcp_user_output selain kcp.output
yang digunakan untuk menentukan perilaku data keluaran objek KCP. kcp.writelog
juga terikat pada fungsi kcp_user_writelog untuk melakukan debug pencetakan.
Selain itu, karena callback kcp.output
tidak dapat memanggil ikcp_input lain secara rekursif (karena pada akhirnya akan berulang ke kcp.output
miliknya sendiri), semua data output harus disimpan di lokasi perantara, dan kemudian dimasukkan ke k2 setelah keluar dari kcp.output
fungsi. Ini adalah tujuan dari struktur OUTPUT_CONTEXT yang ditentukan dalam kode contoh.
Coba jalankan kode contoh dan Anda akan mendapatkan output berikut (konten yang ditambahkan tanda # adalah penjelasannya):
# 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
Konten keluaran adalah informasi debugging yang dicetak ke dalam kode KCP. Awalan baris k1/k2 juga ditambahkan melalui kcp_user_writelog sebagai pembeda.
Diagram skema lengkap dari proses konfirmasi pengiriman kode ini digambarkan sebagai (dua kali ukurannya):
Hubungi ikcp_send di k1: (Gambar langkah 1-1)
Data dengan panjang 4096 ditulis ke pengirim. Menurut kcp.mss
itu dipotong menjadi tiga paket dengan panjang 1376/1376/1344, dan tanda fragmentasi seg.frg
setiap paket masing-masing adalah 2/1/0.
unit transmisi maksimum kcp.mtu
menentukan panjang data maksimum yang diterima oleh panggilan balik ikcp.output
setiap kali.
Dalam diagram skematik, metode ikcp_output pada akhirnya akan memanggil penunjuk fungsi
ikcp.output
. (ikcp.c:212)
Panjang pesan maksimum kcp.mss
dihitung dengan mengurangi overhead protokol (24 byte) dari kcp.mtu
.
Tidak ada callback kcp.output
yang akan dieksekusi saat ini, dan semua data shard akan dialokasikan dan dicatat ke dalam struktur IKCPSEG dan ditambahkan ke antrean kcp.snd_queue
(ikcp.c:528).
Saat ini, panjang antrian kcp.snd_queue
dari k1 adalah 3, dan panjang antrian kcp.snd_buf
adalah 0.
Hubungi ikcp_flush di k1: (Gambar langkah 1-2)
Proses perhitungan spesifik dari jendela diabaikan di sini. Anda hanya perlu mengetahui bahwa nilai jendela kemacetan
kcp.cwnd
adalah 1 ketika k1 memanggil ikcp_flush untuk pertama kalinya.
Karena batasan jendela kemacetan, hanya satu paket yang dapat dikirim untuk pertama kalinya. Objek IKCPSEG dengan panjang data pertama dari antrian kcp.snd_queue
dipindahkan ke antrian kcp.snd_buf
(ikcp.c:1028), dan nilai nomor urut seg.sn
yang ditetapkan sesuai dengan kcp.snd_nxt
adalah 0 (ikcp .c:1036) , bidang seg.cmd
adalah IKCP_CMD_PUSH, Mewakili paket push data.
Saat ini, panjang antrian kcp.snd_queue
dari k1 adalah 2, dan panjang antrian kcp.snd_buf
adalah 1.
Pada langkah 1-3, jalankan panggilan ikcp_output (ikcp.c:1113) pada data pertama yang dikirim untuk mengirimkan paket data [PSH sn=0 frg=2 len=1376]
.
Hanya ada empat jenis perintah data: IKCP_CMD_PUSH (data push) IKCP_CMD_ACK (konfirmasi) IKCP_CMD_WASK (deteksi jendela) IKCP_CMD_WINS (respons jendela), ditentukan di ikcp.c:29
Panggil ikcp_input di k2: (Gambar langkah 2-1)
Masukkan paket data [PSH sn=0 frg=2 len=1376]
, parsing header paket dan periksa validitasnya. (ikcp.c:769)
Analisis jenis paket data dan masukkan pemrosesan cabang dorong data. (ikcp.c:822)
Catat nilai seq.sn
dan nilai seq.ts
paket data ke daftar konfirmasi kcp.acklist
(ikcp.c:828). Mohon diperhatikan : nilai seq.ts
pada contoh ini selalu 0.
Tambahkan paket yang diterima ke antrian kcp.rcv_buf
. (ikcp:709)
Periksa apakah paket data pertama di antrian kcp.rcv_buf
tersedia. Jika paket data tersedia, paket tersebut akan dipindahkan ke antrian kcp.rcv_queue
. (ikcp.c:726)
Paket data yang tersedia di kcp.rcv_buf
didefinisikan sebagai: nomor urut data berikutnya yang diharapkan diterima (diambil dari kcp.rcv_nxt
, di mana nomor urut data berikutnya harus seg.sn
== 0) dan panjang kcp.rcv_queue
Antrian kcp.rcv_queue
lebih kecil dari ukuran Window yang diterima.
Pada langkah ini, satu-satunya paket data yang ada di antrian kcp.rcv_buf
langsung dipindahkan ke antrian kcp.rcv_queue
.
Saat ini, panjang antrian kcp.>rcv_queue
dari k2 adalah 1, dan panjang antrian kcp.snd_buf
adalah 0. Nilai nomor urut data yang diterima berikutnya kcp.rcv_nxt
diperbarui dari 0 menjadi 1.
Hubungi ikcp_flush di k2: (Gbr. Langkah 2-2)
Dalam panggilan ikcp_flush pertama k2. Karena ada data di daftar konfirmasi kcp.acklist
, paket konfirmasi akan dikodekan dan dikirim (ikcp.c:958).
Nilai seg.una
dalam paket konfirmasi ditetapkan kcp.rcv_nxt
=1.
Paket ini dicatat sebagai [ACK sn=0 una=1]
: Artinya dalam konfirmasi ack, nomor urut paket 0 dikonfirmasi. Dalam konfirmasi, semua paket sebelum paket nomor 1 dikonfirmasi.
Pada langkah 2-3, kcp.output
dipanggil untuk mengirim paket data.
Hubungi ikcp_recv di k2: (Gambar Langkah 2-4)
Periksa apakah antrian kcp.rcv_queue
berisi paket dengan nilai seg.frp
0 (ikcp.c:459). Jika berisi paket ini, catat paket pertama seg.frp
== 0 dan data paket sebelumnya paket ini. Panjang total dikembalikan sebagai nilai kembalian. Jika tidak, fungsi ini mengembalikan nilai kegagalan -1.
Karena kcp.rcv_queue
hanya memiliki paket [PSH sn=0 frg=2 len=1376]
saat ini, upaya membaca gagal.
Jika dalam mode streaming (kcp.stream != 0), semua paket akan ditandai sebagai
seg.frg=0
. Saat ini, paket apa pun di antriankcp.rcv_queue
akan berhasil dibaca.
Hubungi ikcp_input: pada k1 (Gambar langkah 3-1)
Paket masukan [ACK sn=0 una=1]
.
UNA menegaskan :
Setiap paket yang diterima akan dicoba konfirmasi UNA terlebih dahulu (ikcp.c:789)
Mengonfirmasi dan menghapus semua paket di antrian kcp.snd_buf
seg.sn
lebih kecil dari nilai una (ikcp:599) dengan mengonfirmasi nilai seg.una
paket tersebut.
[PSH sn=0 frg=2 len=1376]
dikonfirmasi dan dihapus dari antrian kcp.snd_buf
k1.
Konfirmasi ACK :
Analisis jenis paket data dan masukkan pemrosesan cabang konfirmasi. (ikcp.c:792)
Cocokkan nomor urut paket konfirmasi dan hapus paket yang sesuai. (ikcp.c:581)
Saat melakukan konfirmasi ACK pada langkah 3-1, antrian kcp.snd_buf
sudah kosong karena satu-satunya paket [PSH sn=0 frg=2 len=1376]
telah dikonfirmasi oleh UNA terlebih dahulu.
Jika data header antrian kcp.snd_buf
dikonfirmasi ( kcp.snd_una
berubah), nilai cwnd ukuran jendela kemacetan dihitung ulang dan diperbarui menjadi 2 (ikcp.c:876).
Diagram konfirmasi UNA/ACK, diagram ini juga mencatat status kcp.snd_una
yang tidak ditandai dalam diagram proses:
Pengakuan ACK tidak akan berfungsi untuk paket pengakuan yang datang secara berurutan. Untuk paket yang datang tidak sesuai pesanan, paket tersebut akan dihapus satu per satu setelah konfirmasi melalui ACK:
Hubungi ikcp_flush di k1: (Gambar langkah 3-2)
Sama seperti langkah 1-2, nilai jendela kemacetan baru kcp.cwnd
telah diperbarui menjadi 2, dan dua paket sisanya akan dikirim kali ini: [PSH sn=1 frg=1 len=1376]
[PSH sn=2 frg=0 len=1344]
.
Pada langkah 3-3, kcp.output
sebenarnya akan dipanggil dua kali untuk mengirimkan paket data masing-masing.
Hubungi ikcp_input: pada k2 (Gambar langkah 4-1)
Paket masukan [PSH sn=1 frg=1 len=1376]
dan [PSH sn=2 frg=0 len=1344]
.
Setiap paket ditambahkan ke antrian kcp.rcv_buf
, paket tersedia, dan akhirnya semua dipindahkan ke antrian kcp.rcv_queue
.
Saat ini, panjang antrian kcp.rcv_queue
dari k2 adalah 3, dan panjang kcp.snd_buf
adalah 0. Nilai kcp.rcv_nxt
untuk paket berikutnya yang diharapkan diterima diperbarui dari 1 menjadi 3.
Hubungi ikcp_flush di k2: (Gambar langkah 4-2)
Informasi pengakuan di kcp.acklist
akan dikodekan ke dalam paket [ACK sn=1 una=3]
dan [ACK sn=2 una=3]
dan dikirim pada langkah 4-3.
Faktanya, kedua paket ini akan ditulis ke buffer dan panggilan kcp.output
akan dilakukan.
Hubungi ikcp_recv di k2: (Gambar langkah 4-4)
Sekarang ada tiga paket yang belum dibaca di kcp.rcv_queue
: [PSH sn=0 frg=2 len=1376]
[PSH sn=1 frg=1 len=1376]
dan [PSH sn=2 frg=0 len=1344]
Saat ini, paket dengan nilai seg.frg
0 dibaca, dan total panjang yang dapat dibaca dihitung menjadi 4096. Kemudian semua data dalam ketiga paket akan dibaca dan ditulis ke dalam buffer baca dan kesuksesan akan dikembalikan.
Perlu memperhatikan situasi lain : Jika antrian kcp.rcv_queue
berisi 2 paket yang dikirim pengguna dengan nilai seg.frg
2/1/0/2/1/0 dan dipecah menjadi 6 paket data, paket yang sesuai adalah juga perlu memanggil ikcp_recv dua kali untuk membacakan semua data lengkap yang diterima.
Hubungi ikcp_input: pada k1 (Gambar langkah 5-1)
Masukkan paket pengakuan [ACK sn=1 una=3]
dan [ACK sn=2 una=3]
, dan parsing ke seg.una
=3. Paket [PSH sn=1 frg=1 len=1376]
[PSH sn=2 frg=0 len=1344]
dikonfirmasi dan dihapus dari antrian kcp.snd_buf
melalui una.
Semua data yang dikirim telah diakui.
Jendela digunakan untuk kontrol aliran. Ini menandai rentang logis dari antrian. Akibat pengolahan data aktual, posisi antrian terus berpindah ke posisi nomor urut yang tinggi. Logikanya, jendela ini akan terus bergerak dan mengembang serta menyusut secara bersamaan, sehingga disebut juga dengan jendela geser .
Diagram skematik ini merupakan representasi lain dari langkah 3-1 hingga 4-1 diagram alir di bagian "Proses Pengiriman dan Penerimaan Data Dasar". Karena operasi di luar cakupan langkah, arah data ditunjukkan dengan panah semi-transparan.
Semua data diproses melalui fungsi yang ditunjukkan oleh panah dan dipindahkan ke lokasi baru (dua kali ukuran gambar):
Data yang diteruskan oleh fungsi ikcp_send di sisi pengiriman akan langsung disimpan di antrian pengiriman kcp.snd_queue
setelah pemrosesan pemotongan data.
setiap kali ikcp_flush dipanggil. Ukuran jendela untuk transmisi ini akan dihitung berdasarkan ukuran jendela pengiriman kcp.snd_wnd
, ukuran jendela jarak jauh kcp.rmt_wnd
dan ukuran jendela kemacetan kcp.cwnd
Nilainya adalah minimum dari ketiganya: min( kcp.snd_wnd
, kcp.rmt_wnd
, kcp.cwd
) (ikcp.c:1017).
Jika parameter nc diatur ke 1 melalui fungsi ikcp_nodelay dan mode kontrol aliran dimatikan, penghitungan nilai jendela kemacetan diabaikan. Hasil perhitungan jendela pengiriman adalah min( kcp.snd_wnd
, kcp.rmt_wnd
) (ikcp.c:1018).
Pada konfigurasi default hanya mematikan mode kontrol aliran, jumlah paket data yang dapat dikirim untuk pertama kali adalah nilai ukuran default kcp.snd_wnd
32. Ini berbeda dengan contoh proses pengiriman dan penerimaan dasar, di mana hanya satu paket yang dapat dikirim untuk pertama kalinya karena kontrol aliran diaktifkan secara default.
Paket data yang baru terkirim akan dipindahkan ke antrian kcp.snd_buf
.
Untuk data ikcp_send, hanya ada batas irisan sebesar 127 (yaitu 127*
kcp.mss
=174752 byte). Tidak ada batasan jumlah paket dalam antrian pengiriman. Lihat: Cara menghindari penundaan akumulasi cache
Buffer kirim kcp.snd_buf
menyimpan data yang akan atau telah dikirim.
Setiap kali ikcp_flush
dipanggil, jendela pengiriman dihitung dan paket data dipindahkan dari kcp.snd_queue
ke antrian saat ini. Semua paket data dalam antrian saat ini akan diproses dalam tiga situasi:
1. Pengiriman data pertama (ikcp.c:1053)
Berapa kali paket dikirim akan dicatat dalam seg.xmit
. Pemrosesan pengiriman pertama relatif sederhana, dan beberapa parameter seg.rto
/ seg.resendts
untuk batas waktu transmisi ulang akan diinisialisasi.
2. Batas waktu data habis (ikcp.c:1058)
Ketika waktu kcp.current
yang direkam secara internal melebihi periode batas waktu seg.resendts
paket itu sendiri, batas waktu pengiriman ulang terjadi.
3. Konfirmasi penyeberangan data (ikcp.c:1072)
Ketika ujung data direntang dan jumlah rentang seg.fastack
melebihi konfigurasi transmisi ulang rentang kcp.fastresend
, transmisi ulang rentang terjadi. ( kcp.fastresend
defaultnya adalah 0, dan bila 0, dihitung sebagai UINT32_MAX, dan transmisi ulang rentang tidak akan pernah terjadi.) Setelah batas waktu transmisi ulang, paket seg.fastack
saat ini akan diatur ulang ke 0.
Daftar pengakuan adalah daftar catatan sederhana yang awalnya mencatat nomor urut dan cap waktu ( seg.sn
/ seg.ts
) sesuai urutan penerimaan paket.
Oleh karena itu, dalam diagram skema artikel ini,
kcp.ack_list
tidak akan menampilkan posisi elemen kosong apa pun. Karena ini bukan antrian yang diurutkan secara logis (demikian pula, meskipun paket dalam antriansnd_queue
belum diberi nomor urut, nomor urut logisnya telah ditentukan).
Pihak penerima menyangga paket data yang tidak dapat diproses sementara.
Semua paket data yang masuk dari ikcp_input akan tiba di antrian ini terlebih dahulu, dan informasinya akan dicatat ke kcp.ack_list
sesuai urutan kedatangan aslinya.
Hanya ada dua situasi di mana data masih tetap berada dalam antrean ini:
Di sini, paket [PSH sn=0]
diterima terlebih dahulu, yang memenuhi ketentuan paket yang tersedia dan berpindah ke kcp.rev_queue
.
Kemudian paket [PSH sn=2]
diterima, yang bukan merupakan paket berikutnya yang diharapkan diterima ( seg.sn
== kcp.rcv_nxt
), menyebabkan paket ini tetap berada di kcp.rcv_buf
.
Setelah menerima paket [PSH sn=1]
, pindahkan dua paket yang macet [sn=1]
[sn=2]
ke kcp.rcv_queue
.
kcp.rcv_queue
mencapai ukuran jendela penerimaan kcp.rcv_wnd
(ikcp_recv tidak dipanggil tepat waktu).Pihak penerima menyimpan data yang dapat dibaca oleh lapisan atas.
Dalam mode streaming semua paket yang tersedia dibaca, dalam mode non-streaming, segmen data yang terfragmentasi dibaca dan dikumpulkan menjadi data mentah yang lengkap.
Setelah pembacaan selesai, upaya akan dilakukan untuk memindahkan data dari kcp.rcv_buf
ke antrian ini (mungkin untuk memulihkan dari status jendela penerimaan penuh).
Nilai kcp.snd_wnd
jendela pengiriman adalah nilai yang dikonfigurasi, dan defaultnya adalah 32.
Jendela jarak jauh kcp.rmt_wnd
adalah nilai yang akan diperbarui ketika pengirim menerima paket dari penerima (bukan hanya paket pengakuan). Ini mencatat panjang yang tersedia (ikcp.c:1086) dari antrian penerima kcp.rcv_queue
ketika paket data saat ini dikirim oleh pihak penerima.
Jendela kemacetan adalah nilai terhitung yang bertambah secara algoritmik setiap kali data diterima melalui ikcp_input.
Jika paket hilang dan transmisi ulang cepat terdeteksi saat flushing data ikcp_flush, maka akan dihitung ulang sesuai algoritma.
Posisi
kcp.cwnd
diinisialisasi ke 1 ada pada panggilan ikcp_update pertama ke ikcp_flush.
Nilai kcp.rcv_wnd
jendela penerima adalah nilai yang dikonfigurasi, dan defaultnya adalah 128. Ini membatasi panjang maksimum antrian penerimaan kcp.rcv_queue
.
Di bagian ini, versi kcp_optional.c yang ditingkatkan berdasarkan kode contoh di bagian "Pengiriman dan Penerimaan Data Dasar" disediakan. Anda dapat mengamati lebih lanjut perilaku protokol dengan memodifikasi definisi makro.
Kode sampel mengakhiri proses dengan menentukan untuk menulis sejumlah data dengan panjang tetap tertentu ke k1 dan membacanya sepenuhnya di k2.
Makro disediakan untuk mengontrol fungsi tertentu:
Jendela kemacetan dicatat melalui nilai kcp.cwnd
dan kcp.incr
. Karena unit yang direkam oleh kcp.cwnd
adalah paket, kcp.incr
tambahan diperlukan untuk mencatat jendela kemacetan yang dinyatakan dalam satuan panjang byte.
Seperti TCP, pengendalian kemacetan KCP juga dibagi menjadi dua tahap: start lambat dan penghindaran kemacetan:
Jendela kemacetan bertambah ; dalam proses konfirmasi paket data, setiap kali data header antrian kcp.snd_buf
dikonfirmasi (konfirmasi UNA efektif, kcp.snd_una
berubah). Dan ketika jendela kemacetan lebih kecil dari jendela jarak jauh yang direkam kcp.rmt_wnd
, jendela kemacetan bertambah. (ikcp:875)
1. Jika jendela kemacetan lebih kecil dari ambang batas mulai lambat kcp.ssthresh
, maka jendela tersebut termasuk dalam tahap mulai lambat , dan pertumbuhan jendela kemacetan relatif agresif pada saat ini. Jendela kemacetan bertambah satu unit.
2. Jika jendela kemacetan lebih besar atau sama dengan ambang batas start lambat, maka jendela tersebut termasuk dalam tahap penghindaran kemacetan , dan pertumbuhan jendela kemacetan relatif konservatif. Jika kcp.incr
meningkatkan mss/16 setiap kali, diperlukan 16 konfirmasi UNA yang valid sebelum jendela kemacetan unit ditingkatkan. Pertumbuhan jendela fase penghindaran kemacetan yang sebenarnya adalah:
(mss * mss) / incr + (mss / 16)
Karena incr=cwnd*mss adalah:
((mss * mss) / (cwnd * mss)) + (mss / 16)
Setara dengan:
(mss / cwnd) + (mss / 16)
Jendela kemacetan bertambah secara bertahap untuk setiap cwnd dan setiap 16 pengakuan UNA yang valid.
Pengurangan jendela kemacetan : Ketika fungsi ikcp_flush mendeteksi kehilangan paket selama transmisi ulang atau batas waktu, jendela kemacetan berkurang.
1. Ketika transmisi ulang rentang terjadi, ambang batas mulai lambat kcp.ssthresh
diatur ke setengah rentang nomor urut yang tidak diakui. Ukuran jendela kemacetan adalah ambang batas mulai lambat ditambah nilai konfigurasi transmisi ulang cepat kcp.resend
(ikcp:1117):
ssthresh = (snd_nxt - snd_una) / 2
cwnd = ssthresh + resend
2. Ketika batas waktu kehilangan paket terdeteksi, ambang batas mulai lambat diatur ke setengah dari jendela kemacetan saat ini. Jendela kemacetan disetel ke 1 (ikcp:1126):
ssthresh = cwnd / 2
cwnd = 1
Amati mulai lambat 1 : Jalankan kode contoh dengan konfigurasi default dan Anda akan melihat bahwa proses mulai lambat dilewati dengan cepat. Ini karena ambang awal lambat default adalah 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 pada konten keluaran adalah waktu logis, n adalah berapa kali k1 mengirimkan data dalam siklus, nilai cwnd=1|1 menunjukkan bahwa 1 di depan simbol batang vertikal adalah jendela yang dihitung ketika ikcp_flush, itu adalah, min(kcp. dalam mode kontrol aliran. snd_wnd, kcp.rmt_wnd, kcp.cwnd), nilai 1 berikut adalah kcp.cwnd
.
Amati pertumbuhan jendela kemacetan pada konfigurasi default secara grafis: ketika berada dalam fase penghindaran kemacetan, semakin besar jendela kemacetan, semakin lancar pertumbuhan jendela kemacetan.
Amati start lambat 2 : Sesuaikan konfigurasi sampel ambang batas start lambat nilai awal KCP_THRESH_INIT menjadi 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
...
Dengan hanya mencegat beberapa saat sebelum putaran transmisi, dapat diamati bahwa start yang lambat juga meningkat secara linier secara default.
Amati mematikan pengakuan tertunda : kirim data sebanyak mungkin, dan matikan opsi pengiriman tertunda ACK_DELAY_FLUSH , dan simulasikan kehilangan paket:
#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
...
Dalam hal ini, grafik klasik dari start yang lambat dan penghindaran kemacetan diperoleh. Kehilangan paket terdeteksi pada putaran transmisi ke-15 (t=1500):
Menonaktifkan opsi pengiriman tertunda berarti pihak penerima data akan segera menghubungi ikcp_flush setelah setiap eksekusi ikcp_input untuk mengirimkan kembali paket konfirmasi.
Proses mulai lambat saat ini meningkat secara eksponensial setiap RTT (Round-Trip Time, round trip time), karena setiap paket pengakuan akan dikirim secara independen, menyebabkan jendela kemacetan pengirim bertambah, dan karena jendela kemacetan bertambah, jumlah paket dikirim dalam setiap RTT akan meningkat dua kali lipat.
Jika konfirmasi tertunda, paket konfirmasi akan dikirim bersamaan. Proses peningkatan jendela kemacetan hanya akan dijalankan satu kali setiap kali fungsi ikcp_input dipanggil, sehingga menggabungkan beberapa paket pengakuan yang diterima tidak akan berdampak pada peningkatan jendela kemacetan berkali-kali.
Jika interval siklus jam lebih besar dari RTT, maka setiap interval akan meningkat secara eksponensial. Diagram skematik menunjukkan situasi yang mungkin terjadi:
Premis pertumbuhan eksponensial adalah bahwa data yang dikirim berikutnya dapat memenuhi dua kali jumlah paket data terakhir kali. Jika data yang ditulis ke pihak pengiriman tidak mencukupi, pertumbuhan eksponensial tidak akan tercapai.
Perlu dicatat bahwa meskipun kode contoh mengirimkan konfirmasi langsung, itu hanya mempengaruhi cara pihak penerima mengirimkan konfirmasi. Pengirim juga perlu menunggu siklus berikutnya sebelum memproses paket konfirmasi tersebut. Oleh karena itu, waktu t di sini hanya untuk referensi. Kecuali jika paket yang diterima tidak segera diproses dan disimpan dalam kode transceiver jaringan yang sebenarnya, paket tersebut harus menunggu hingga siklus pembaruan sebelum merespons.
Berdasarkan karakteristik KCP, harus ada cara yang lebih langsung untuk memperbesar jendela kemacetan, dan langsung mematikan kontrol aliran untuk mendapatkan pengiriman yang lebih agresif:
#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
Anda juga dapat langsung mengubah nilai jendela kemacetan sesuai kebutuhan.
Amati bahwa jendela jarak jauh penuh : Jika panjang data yang dikirim mendekati ukuran jendela jarak jauh default dan pihak penerima tidak membacanya tepat waktu, Anda akan menemukan periode di mana tidak ada data yang dapat dikirim (perhatikan bahwa dalam kode contoh, pihak penerima terlebih dahulu mengirimkan paket konfirmasi dan kemudian Baca isinya lagi):
#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
Ketika ukuran jendela jarak jauh kcp.rmt_wnd
yang direkam di penerima adalah 0, fase tunggu probe (probe wait, ikcp.c:973) akan dimulai di ikcp_flush. kcp.ts_probe
akan mencatat waktu awalnya 7000 milidetik (dicatat dalam kcp->probe_wait
).
Ketika waktunya tiba, enkode tambahan paket bertipe IKCP_CMD_WASK dan kirimkan ke pihak penerima (ikcp.c:994) untuk meminta pihak jarak jauh mengirim kembali paket bertipe IKCP_CMD_WINS untuk memperbarui kcp.rmt_wnd
Jika ukuran jendela jarak jauh selalu 0, kcp->probe_wait
meningkat setengah dari nilai saat ini setiap kali, dan kemudian memperbarui waktu tunggu maksimum adalah 120000 milidetik (120 detik).
Jika ukuran jendela jarak jauh bukan 0, status deteksi di atas akan dihapus.
Dalam contoh ini, kami tidak menunggu 7 detik awal sebelum memulihkan ukuran jendela jarak jauh yang direkam. Karena dalam pengoperasian ikcp_recv di sisi penerima untuk membaca data, ketika panjang antrian kcp.rcv_queue
lebih besar dari atau sama dengan jendela penerima kcp.rcv_wnd
sebelum membaca data (jendela pembacaan penuh), sedikit tanda (ikcp .c:431) dan kirim paket bertipe IKCP_CMD_WINS (ikcp.c:1005) lain kali ikcp_flush Untuk memberi tahu pihak pengirim agar memperbarui ukuran jendela jarak jauh terbaru.
Untuk menghindari masalah ini, data perlu dibacakan pada pihak penerima secara tepat waktu. Namun, bahkan jika jendela jarak jauh menjadi lebih kecil, jendela pengiriman pada pengirim akan menjadi lebih kecil, yang mengakibatkan penundaan tambahan. Pada saat yang sama, perlu juga untuk meningkatkan jendela penerimaan dari pihak penerima.
Coba ubah nilai RECV_TIME ke nilai yang relatif besar (misalnya 300 detik), lalu amati pengiriman paket IKCP_CMD_WASK.
Seperti yang dijelaskan pada deskripsi antrian kcp.snd_buf
, saat memanggil ikcp_flush, semua paket dalam antrian akan dilalui, jika paket tidak dikirim untuk pertama kali. Kemudian ia akan memeriksa apakah paket telah melewati pengakuan sebanyak yang ditentukan, atau apakah periode waktu habis telah tercapai.
Bidang yang terkait dengan perhitungan waktu pulang pergi dan batas waktu adalah:
kcp.rx_rttval
: waktu jitter jaringan lancarkcp.rx_srtt
: Waktu perjalanan pulang pergi yang lancarkcp.rx_rto
(Batas Waktu Penerimaan Transmisi Ulang): batas waktu transmisi ulang, nilai awal 200kcp.rx_minrto
: Batas waktu transmisi ulang minimum, nilai awal 100kcp.xmit
: jumlah transmisi ulang globalseg.resendts
: stempel waktu transmisi ulangseg.rto
: batas waktu transmisi ulangseg.xmit
: jumlah transmisi ulangSebelum membahas cara paket menghitung batas waktu, pertama-tama mari kita lihat bagaimana kolom terkait waktu pulang-pergi dan batas waktu habis dihitung:
Catatan waktu pulang pergi : Setiap kali paket konfirmasi ACK diproses, paket konfirmasi akan membawa nomor urut dan waktu pengiriman nomor urut ke pengirim ( seg.sn
/ seg.ts
). waktu proses pembaruan dijalankan.
Nilai RTT adalah waktu pulang -pergi dari satu paket, yaitu, rtt = kcp.current
- seg.ts
Jika waktu perjalanan bundar yang dihaluskan, kcp.rx_srtt
adalah 0, itu berarti bahwa inisialisasi dilakukan: kcp.rx_srtt
secara langsung direkam sebagai RTT, kcp.rx_rttval
dicatat sebagai setengah dari RTT.
Dalam proses non-inisialisasi, nilai delta dihitung, yang mewakili nilai fluktuasi RTT ini dan kcp.rx_srtt
yang direkam (IKCP.C: 550):
delta = abs(rtt - rx_srtt)
kcp.rx_rttval
baru diperbarui dengan nilai tertimbang dari kcp.rx_rttval
dan Delta yang lama:
rx_rttval = (3 * rx_rttval + delta) / 4
kcp.rx_srtt
baru diperbarui dengan nilai tertimbang dari kcp.rx_srtt
dan RTT lama, dan tidak dapat kurang dari 1:
rx_srtt = (7 * rx_srtt + rtt) / 8
rx_srtt = max(1, rx_srtt)
rx_rto
baru diperbarui dengan nilai minimum dari waktu perjalanan bundar yang dihaluskan kcp.rx_srtt
plus siklus clock kcp.interval
dan 4 kali rx_rttval
, dan kisarannya terbatas pada [ kcp.rx_minrto
, 60000]:
rto = rx_srtt + max(interval, 4 * rttval)
rx_rto = min(max(`kcp.rx_minrto`, rto), 60000)
Idealnya, ketika jaringan hanya memiliki penundaan yang tetap dan tidak ada jitter, nilai kcp.rx_rttval
akan mendekati 0, dan nilai kcp.rx_rto
ditentukan oleh waktu perjalanan bulat yang mulus dan siklus clock.
Diagram Perhitungan Waktu Perjalanan Smooth:
Waktu Pengiriman Kontrak Pertama (IKCP.C: 1052):
seg.rto
akan merekam status kcp.rx_rto
, dan waktu waktu pertama dari paket data adalah seg.rto
+ rtomin Milliconds.
rtomin dihitung dengan kcp.rx_rto
, jika mode nodelay diaktifkan. rtomin adalah 0, jika tidak kcp.rx_rto
/8.
Timeout untuk Nodelay tidak diaktifkan:
resendts = current + rx_rto + rx_rto / 8
Aktifkan Nodelay Timeout:
resendts = current + rx_rto
Retransmisi Batas waktu (IKCP.C: 1058):
Ketika waktu internal mencapai waktu batas waktu seg.resendts
.
Ketika Mode Nodelay tidak diaktifkan, kenaikan seg.rto
adalah MAX ( seg.rto
, kcp.rx_rto
) (pertumbuhan ganda):
rto += max(rto, rx_rto)
Ketika Nodelay diaktifkan dan Nodelay adalah 1, tingkatkan seg.rto
hingga setengah setiap kali (peningkatan 1,5 kali):
rto += rto / 2
Ketika Nodelay diaktifkan dan Nodelay adalah 2, kcp.rx_rto
meningkat setengahnya setiap kali (peningkatan 1,5 kali):
rto += rx_rto / 2
Timeout baru setelah seg.rto
milidetik:
resendts = current + rx_rto
Retransmisi Melintasi Waktu (IKCP.C: 1072):
Ketika paket data dilintasi beberapa kali, retransmisi persimpangan dipicu.
seg.rto
tidak diperbarui saat mentransmisikan kembali waktu, dan waktu pengiriman ulang waktu tadi berikutnya secara langsung dihitung ulang:
resendts = current + rto
Amati batas waktu default
Hanya kirim satu paket, jatuhkan empat kali, dan amati waktu tunggu dan transmisi ulang.
Untuk konfigurasi default, nilai awal kcp.rx_rto
adalah 200 milidetik, dan waktu waktu pertama adalah 225 milidetik. .
#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
Amati peningkatan 1,5x dalam RTO berdasarkan paket itu sendiri
#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
Tonton pertumbuhan 1,5x berdasarkan 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
Mengamati transmisi rentang
Paket pengakuan yang diproses dengan [sn=0]
tidak akan memicu [sn=1]
rentang [sn=2]
Pada akhirnya, itu diirim ulang melalui waktu tunggu.
#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
Saat mengirim paket data dalam dua langkah, grup paket kedua akan dilintasi dua kali saat melakukan konfirmasi IKCP_PUT, dan [sn=0]
akan diakumulasikan dan ditransmisikan ulang selama IKCP_FLUSH berikutnya.
#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
Artikel ini dilisensikan di bawah lisensi internasional Atribution-NonCommercial-Noderivs 4.0 Creative Commons.
Kode dalam proyek adalah open source menggunakan lisensi MIT.
Tentang Font Gambar: Noto Sans SC