KCP是一种基于UDP的上层协议,项目地址:KCP – A Fast and Reliable ARQ Protocol,以下是github上的介绍:
KCP是一个快速可靠协议,能以比 TCP浪费10%-20%的带宽的代价,换取平均延迟降低 30%-40%,且最大延迟降低三倍的传输效果。纯算法实现,并不负责底层协议(如UDP)的收发,需要使用者自己定义下层数据包的发送方式,以 callback的方式提供给 KCP。 连时钟都需要外部传递进来,内部不会有任何一次系统调用。
整个协议只有 ikcp.h, ikcp.c两个源文件,可以方便的集成到用户自己的协议栈中。也许你实现了一个P2P,或者某个基于 UDP的协议,而缺乏一套完善的ARQ可靠协议实现,那么简单的拷贝这两个文件到现有项目中,稍微编写两行代码,即可使用。
简单来说,就是在UDP上再做了一层封装,来实现TCP的效果并且弥补TCP的一些不足。
KCP协议是个二进制协议,当然,传输层上的封装,为了保持效率,使用二进制协议也是理所当然的。
KCP的帧头结构
KCP帧头8字节对齐,KCP空包大小为24字节。
+-------+-------+-------+-------+-------+-------+-------+-------+
| conv | cmd | frg | wnd |
+-------+-------+-------+-------+-------+-------+-------+-------+
| ts | sn |
+-------+-------+-------+-------+-------+-------+-------+-------+
| una | len |
+-------+-------+-------+-------+-------+-------+-------+-------+
| |
* data *
| |
+-------+-------+-------+-------+-------+-------+-------+-------+
conv(conversation)
4字节,会话标识。
我们知道UDP不是面向连接的,因此KCP自己实现了一套会话机制。conv由客户端随机生成,之后客户端和服务端交互的conv值均一致,以此来标识两个peer之间的通信是一个会话。
cmd(command)
1字节,是标识KCP包的功能。有以下几种:
- IKCP_CMD_PUSH = 81 // cmd: push data
- IKCP_CMD_ACK = 82 // cmd: ack
- IKCP_CMD_WASK = 83 // cmd: window probe (ask)
- IKCP_CMD_WINS = 84 // cmd: window size (tell)
frg(fragment)
1字节,分段序号。
KCP有两种模式,一种是stream模式,一种是message模式。当为stream模式时,frg的值始终为0。当为message模式时,传输的数据大小超过MTU限制,会被分成多个包,通过frg来标识不同包的序号,使得在不知道包到达的先后顺序的情况下也能够通过frg字段来重新按照顺序组装成原始数据。
NOTE:在使用kcp-go库的时候,会在写入KCP之前进行分包操作,因此无论数据多大,frg的值始终为0。至于库作者为什么这么做,暂时不清楚。这样会更安全一些,否则发送一个超大的包,在KCP core内部进行分包时,包的数量可能大于255,而frg为固定1字节长,因此会出错,为了避免发生这样的错误,提前做了分包操作。
wnd
1字节,窗口大小,自己(发送当前包的peer)能够接收数据量。
窗口大小可以为0,当为0时,在发送数据前会进行探测窗口大小的操作。
ts
4字节,当前毫秒时间戳。
sn
4字节,包序号。
该包序号为当前会话中的序号,从0开始。
una
4字节,此编号前所有的包都已收到。
ARQ模型响应有两种,UNA(此编号前所有包已收到,如TCP)和ACK(该编号包已收到),光用UNA将导致全部重传,光用ACK则丢失成本太高,以往协议都是二选其一,而 KCP协议中,除去单独的 ACK包外,所有包都有UNA信息。
len
4字节,数据部分的长度。
前向纠错
在KCP的go版本实现中还加入了前向纠错。具体实现为发送数据包后,再发送n个纠错包。其中,n由参数parityShards确定,n = parityShards。
帧头格式:
+-------+-------+-------+-------+-------+-------+-------+-------+
| next | type | len |
+-------+-------+-------+-------+-------+-------+-------+-------+
next
4字节,下一个FEC包的seq值。
type
2字节,有两种值:typeData和typeFEC。
在数据包中,为typeData。纠错包中为typeFEC。
len
该值为数据包中的数据长度,包括KCP帧头和该字段的两个字节。
数据加密
同样,kcp-go中还可以启用数据加密,帧头格式:
+-------+-------+-------+-------+-------+-------+-------+-------+
| nonce |
+-------+-------+-------+-------+-------+-------+-------+-------+
| nonce |
+-------+-------+-------+-------+-------+-------+-------+-------+
| crc32 |
+-------+-------+-------+-------+
nonce
16字节。
nonce的生成方式为首先判断本次会话中上次发包的nonce的第一个字节是否是0,如果是,则重新生成随机字符串,然后MD5后作为当前包的nonce。如果不是,则不重新生成字符串,仅对上一个包的nonce做md5后作为当前包的nonce。重新生成随机字符串的概率为1/256.
crc32
4字节。
该值为除去数据加密的帧头外其他数据(包括FEC帧头)的crc32值(IEEE)。
加密
加密数据为以上所有数据,包括加密帧头、前向纠错帧头、KCP帧头。
加入前向纠错和数据加密后,一个完整的数据包帧头格式为:
+-------+-------+-------+-------+
| nonce |
+-------+-------+-------+-------+-------+-------+-------+-------+
| nonce |
+-------+-------+-------+-------+-------+-------+-------+-------+
| nonce | crc32 |
+-------+-------+-------+-------+-------+-------+-------+-------+
| next | type | len |
+-------+-------+-------+-------+-------+-------+-------+-------+
| conv | cmd | frg | wnd |
+-------+-------+-------+-------+-------+-------+-------+-------+
| ts | sn |
+-------+-------+-------+-------+-------+-------+-------+-------+
| una | len |
+-------+-------+-------+-------+-------+-------+-------+-------+
| |
* data *
| |
+-------+-------+-------+-------+-------+-------+-------+-------+