KCP介紹
1 簡(jiǎn)介
KCP是一個(gè)快速可靠協(xié)議题画,能以比 TCP浪費(fèi)10%-20%的帶寬的代價(jià)悔橄,換取平均延遲降低 30%-40%,且最大延遲降低三倍的傳輸效果留储。純算法實(shí)現(xiàn)毅戈,并不負(fù)責(zé)底層協(xié)議(如UDP)的收發(fā)纵刘,需要使用者自己定義下層數(shù)據(jù)包的發(fā)送方式腻豌,以 callback的方式提供給 KCP引谜。 連時(shí)鐘都需要外部傳遞進(jìn)來厦画,內(nèi)部不會(huì)有任何一次系統(tǒng)調(diào)用疮茄。
2 技術(shù)特性
TCP是為流量設(shè)計(jì)的(每秒內(nèi)可以傳輸多少KB的數(shù)據(jù)),講究的是充分利用帶寬根暑。而 KCP是為流速設(shè)計(jì)的(單個(gè)數(shù)據(jù)包從一端發(fā)送到一端需要多少時(shí)間)力试,以10%-20%帶寬浪費(fèi)的代價(jià)換取了比 TCP快30%-40%的傳輸速度。TCP信道是一條流速很慢购裙,但每秒流量很大的大運(yùn)河懂版,而KCP是水流湍急的小激流。KCP有正常模式和快速模式兩種躏率,通過以下策略達(dá)到提高流速的結(jié)果:
RTO翻倍vs不翻倍:
TCP超時(shí)計(jì)算是RTOx2躯畴,這樣連續(xù)丟三次包就變成RTOx8了,十分恐怖薇芝,而KCP啟動(dòng)快速模式后不x2蓬抄,只是x1.5(實(shí)驗(yàn)證明1.5這個(gè)值相對(duì)比較好),提高了傳輸速度夯到。
選擇性重傳 vs 全部重傳:
TCP丟包時(shí)會(huì)全部重傳從丟的那個(gè)包開始以后的數(shù)據(jù)嚷缭,KCP是選擇性重傳,只重傳真正丟失的數(shù)據(jù)包耍贾。
快速重傳:
發(fā)送端發(fā)送了1,2,3,4,5幾個(gè)包阅爽,然后收到遠(yuǎn)端的ACK: 1, 3, 4, 5,當(dāng)收到ACK3時(shí)荐开,KCP知道2被跳過1次付翁,收到ACK4時(shí),知道2被跳過了2次晃听,此時(shí)可以認(rèn)為2號(hào)丟失百侧,不用等超時(shí),直接重傳2號(hào)包能扒,大大改善了丟包時(shí)的傳輸速度佣渴。
延遲ACK vs 非延遲ACK:
TCP為了充分利用帶寬,延遲發(fā)送ACK(NODELAY都沒用)初斑,這樣超時(shí)計(jì)算會(huì)算出較大 RTT時(shí)間辛润,延長(zhǎng)了丟包時(shí)的判斷過程。KCP的ACK是否延遲發(fā)送可以調(diào)節(jié)见秤。
UNA vs ACK+UNA:
ARQ模型響應(yīng)有兩種频蛔,UNA(此編號(hào)前所有包已收到灵迫,如TCP)和ACK(該編號(hào)包已收到),光用UNA將導(dǎo)致全部重傳晦溪,光用ACK則丟失成本太高瀑粥,以往協(xié)議都是二選其一,而 KCP協(xié)議中三圆,除去單獨(dú)的 ACK包外狞换,所有包都有UNA信息。
非退讓流控:
KCP正常模式同TCP一樣使用公平退讓法則舟肉,即發(fā)送窗口大小由:發(fā)送緩存大小修噪、接收端剩余接收緩存大小、丟包退讓及慢啟動(dòng)這四要素決定路媚。但傳送及時(shí)性要求很高的小數(shù)據(jù)時(shí)黄琼,可選擇通過配置跳過后兩步,僅用前兩項(xiàng)來控制發(fā)送頻率整慎。以犧牲部分公平性及帶寬利用率之代價(jià)脏款,換取了開著BT都能流暢傳輸?shù)男Ч?/p>
3 協(xié)議定義
3.1 kcp協(xié)議
type?segment?struct?{
// 發(fā)送端與接收端通信時(shí)的匹配數(shù)字,發(fā)送端發(fā)送的數(shù)據(jù)包中此值與接收端的conv值匹配一致時(shí)裤园,接收端才會(huì)接受此包
conv uint32
// 改數(shù)據(jù)包的協(xié)議號(hào)撤师,協(xié)議號(hào)有以下枚舉:
// IKCP_CMD_PUSH = 81 // cmd: push data,數(shù)據(jù)包
// IKCP_CMD_ACK = 82 // cmd: ack拧揽,確認(rèn)包剃盾,告訴對(duì)方收到數(shù)據(jù)包
// IKCP_CMD_WASK = 83 // cmd: window probe (ask),詢問遠(yuǎn)端滑動(dòng)窗口的大小
// IKCP_CMD_WINS = 84 // cmd: window size (tell)淤袜,告知遠(yuǎn)端滑動(dòng)窗口的大小
cmd uint8
// 分幀號(hào)痒谴,由于udp傳輸有數(shù)據(jù)包大小的限制,因此铡羡,應(yīng)用層一個(gè)數(shù)據(jù)包可能被分為多個(gè)udp包
frg uint8
// 滑動(dòng)窗口的大小
// 當(dāng)Segment做為發(fā)送數(shù)據(jù)時(shí)积蔚,此wnd為本機(jī)滑動(dòng)窗口大小,用于告訴遠(yuǎn)端自己窗口剩余多少
// 當(dāng)Segment做為接收到數(shù)據(jù)時(shí)蓖墅,此wnd為遠(yuǎn)端滑動(dòng)窗口大小库倘,本機(jī)知道了遠(yuǎn)端窗口剩余多少后临扮,可以控制自己接下來發(fā)送數(shù)據(jù)的大小
wnd uint16
// timestamp , 當(dāng)前Segment發(fā)送時(shí)的時(shí)間戳
ts uint32
// Sequence Number,Segment數(shù)據(jù)包的編號(hào)
sn uint32
// una即unacknowledged,未確認(rèn)數(shù)據(jù)包的編號(hào)论矾,表示此編號(hào)前的所有包都已收到了。
una uint32
// rto即Retransmission TimeOut杆勇,即超時(shí)重傳時(shí)間贪壳,在發(fā)送出去時(shí)根據(jù)之前的網(wǎng)絡(luò)情況進(jìn)行設(shè)置
rto uint32
// 基本類似于Segment發(fā)送的次數(shù),每發(fā)送一次會(huì)自加一蚜退。用于統(tǒng)計(jì)該Segment被重傳了幾次闰靴,用于參考彪笼,進(jìn)行調(diào)節(jié)
xmit uint32
// 即resend timestamp , 指定重發(fā)的時(shí)間戳,當(dāng)當(dāng)前時(shí)間超過這個(gè)時(shí)間時(shí)蚂且,則再重發(fā)一次這個(gè)包配猫。
resendts uint32
// 用于以數(shù)據(jù)驅(qū)動(dòng)的快速重傳機(jī)制;
fastack uint32
// len uint32 c++版本有數(shù)據(jù)包的數(shù)據(jù)長(zhǎng)度杏死,go版本無此字段
// 協(xié)議數(shù)據(jù)的具體內(nèi)容
data []byte
}
3.2 enet協(xié)議對(duì)比
typedef struct _ENetProtocolHeader
{
enet_uint16 peerID;
enet_uint16 sentTime;
} ENET_PACKED ENetProtocolHeader;
typedef struct _ENetProtocolCommandHeader
{
enet_uint8 command;
enet_uint8 channelID;
enet_uint16 reliableSequenceNumber;
} ENET_PACKED ENetProtocolCommandHeader;
4 流程圖
4.1 發(fā)送流程
4.2 接收流程