「协议」Kcp协议介绍、Demo讲解与工作过程浅谈

Tcp和Udp

传输控制协议(英语:Transmission Control Protocol,缩写:TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,其拥有着相对而言的可靠传输(相对UDP),由于Tcp的相关特性如在连接之前先创建两端的虚拟连接,以及发送数据的超时重传、滑动窗口、流量/拥塞控制等特性保证了其可靠的传输,因而TCP通常会保证数据准确交付。

但由于其在穿输数据之前需要进行虚拟连接的建立,这回消耗一定的传输时间,且在传输过程之中为保证数据正确交付而采用的超时重传、滑动窗口、流量/拥塞控制等机制也会消耗大量的传输时间,另外由于建立TCP需要进行虚拟连接建立,因此可能会被利用从而收到DDOS,DOS等攻击。

总的来说,Tcp算是一种消耗一定的传输性能而确保数据准确到达对端的一种协议,当需要网络质量很高时,需要采用Tcp协议。

用户数据报协议(英语:User Datagram Protocol,缩写:UDP;又称用户数据包协议)是一个简单的面向数据报的通信协议,其没有Tcp的发送前建立连接这一特性因此其是一个无状态协议,同时也没有Tcp中确保数据准确到达的一些保证机制,所以对于Tcp来说Udp的传输很快速,但是从而引发出了Udp协议的是不可靠的传输协议,当网络波动较大时,丢包率会很高,所以Udp保证数据快速到达但不能保证必定到达(管杀不管埋?),且虽然Udp无需建立连接,也就可能较Tcp被攻击者利用的漏洞就要少一些。但是其仍然无法避免被攻击。

快速的Tcp?可靠的Udp?

那么有没有一种协议可以既拥有Tcp的可靠性,又拥有Udp的快速性呢?(既要也要你想🍑)

诶,还真有,那就是本篇文章的主角Kcp协议,Kcp是个啥呢?其官网是这么介绍的:

KCP是一个快速可靠协议,能以比 TCP 浪费 10%-20% 的带宽的代价,换取平均延迟降低 30%-40%,且最大延迟降低三倍的传输效果。纯算法实现,并不负责底层协议(如UDP)的收发,需要使用者自己定义下层数据包的发送方式,以 callback的方式提供给 KCP。 连时钟都需要外部传递进来,内部不会有任何一次系统调用。

TCP是为流量设计的(每秒内可以传输多少KB的数据),讲究的是充分利用带宽。而 KCP是为流速设计的(单个数据包从一端发送到一端需要多少时间),以10%-20%带宽浪费的代价换取了比 TCP快30%-40%的传输速度。TCP信道是一条流速很慢,但每秒流量很大的大运河,而KCP是水流湍急的小激流。KCP有正常模式和快速模式两种,通过以下策略达到提高流速的结果。

理论上来说Kcp其实并不是一个传输层协议, 其是在传输层协议(一般都使用Udp作为基础,下文也以Udp为基础展开)的基础之上,通过算法方式实现ARQ模型以及可靠传输的应用层协议。

此处存疑,因为Kcp不算是一个传输层协议,但是网络上很多文章都将其算作为是一个具有可靠性的传输层ARQ协议,作者对此表示不解,如果有大神了解此问题,请后台留言,再次先做感谢。

Kcp协议格式

那么Kcp通过何种方式来保证其传输可靠性呢?首先来看一下Kcp协议的协议头的组成格式。

协议头

  • Conv :连接号,因为其底层基于Udp,Udp无连接,所以需要一个连接号来确定本次消息来自于哪个客户端。相当于代替了虚拟连接。
  • Cmd:控制字符,其中包括IKCP_CMD_PUSH(推送数据)、IKCP_CMD_ACK(回应)、IKCP_CMD_WASK(问问客户端接口窗口多大)、IKCP_CMD_WINS(告诉服务器接收窗口多大)
  • Frg:分片,要传输的数据会分成几个Kcp包丢出去。
  • Wnd:窗口大小。
  • Ts:时间序列。
  • Sn:Kcp包的序列号。
  • Una:确认包的序列号,比如收到sn=1的包,回复的Ack包的una就为sn+1。
  • Len:数据长度。
  • Data:实际传输的用户数据,默认的Mtu为1400Byte。

在Kcp中每次的数据传输通常都是基于Udp的,也就是说在传输过程中,实际上Kcp的协议头是被包含在Udp的协议的数据包之中的数据字段中(自行前往了解Udp协议头长啥样)的,举个例子,下面的Byte数组打印结果是一个通过udp.ReadFromUdp接收到的Udp数据包,其中包含了Kcp的协议头以及数据,我们来看一下他长啥样:

在这里插入图片描述

从上图中可以看到,Kcp的协议头及其数据都是包含在Udp数据包的数据字段中的,因此可以说Kcp是基于Udp的,且其不是一个传输层协议而是一个应用层协议(此处仍然存疑)。

为什么可靠?为什么快速?

Kcp协议的开发者表示,KCP是一个快速可靠协议,那么可靠在哪里快速在哪里?众所周知,Tcp协议的可靠性是由连接之前先创建两端的虚拟连接,以及发送数据的超时重传、滑动窗口、流量/拥塞控制等特性保证了其可靠的传输,那么Kcp为何可靠且快速呢?官网中给了如下的解释,以下的保证可靠的方式均由纯算法完成。

RTO翻倍vs不翻倍:

TCP超时计算是RTOx2,这样连续丢三次包就变成RTOx8了,十分恐怖,而KCP启动快速模式后不x2,只是x1.5(实验证明1.5这个值相对比较好),提高了传输速度。

选择性重传 vs 全部重传:

TCP丢包时会全部重传从丢的那个包开始以后的数据,KCP是选择性重传,只重传真正丢失的数据包。

快速重传:

发送端发送了1,2,3,4,5几个包,然后收到远端的ACK: 1, 3, 4, 5,当收到ACK3时,KCP知道2被跳过1次,收到ACK4时,知道2被跳过了2次,此时可以认为2号丢失,不用等超时,直接重传2号包,大大改善了丢包时的传输速度。

延迟ACK vs 非延迟ACK:

TCP为了充分利用带宽,延迟发送ACK(NODELAY都没用),这样超时计算会算出较大 RTT时间,延长了丢包时的判断过程。KCP的ACK是否延迟发送可以调节。

UNA vs ACK+UNA:

ARQ模型响应有两种,UNA(此编号前所有包已收到,如TCP)和ACK(该编号包已收到),光用UNA将导致全部重传,光用ACK则丢失成本太高,以往协议都是二选其一,而 KCP协议中,除去单独的 ACK包外,所有包都有UNA信息。

非退让流控:

KCP正常模式同TCP一样使用公平退让法则,即发送窗口大小由:发送缓存大小、接收端剩余接收缓存大小、丢包退让及慢启动这四要素决定。但传送及时性要求很高的小数据时,可选择通过配置跳过后两步,仅用前两项来控制发送频率。以牺牲部分公平性及带宽利用率之代价,换取了开着BT都能流畅传输的效果。

Kcp-go包的Demo

Kcp协议各个语言几乎都对Kcp协议进行了实现,Go也不例外,Go的Kcp实现库如下:

https://github.com/xtaci/kcp-go

kcp-go is a Production-Grade Reliable-UDP library for golang.

This library intents to provide a smooth, resilient, ordered, error-checked and anonymous delivery of streams over UDP packets, it has been battle-tested with opensource project kcptun. Millions of devices(from low-end MIPS routers to high-end servers) have deployed kcp-go powered program in a variety of forms like online games, live broadcasting, file synchronization and network acceleration.

由于这个库对Kcp的原生特性进行了包装,给的案例也是基于封装之后的,其将Kcp的底层做了封装,那么接下来我把封装揭开,直接使用Kcp进行Demo的制作。

type Conv = uint32

// 案例是Kcp Server 但是只要把kcpList的类型从map换成
// *kcp.KCP 就可以当做一个客户端用了
type KcpServer struct {
   
   kcpList map[Conv]*kcp.KCP
}

func NewKcpServer() *KcpServer {
   
   return &KcpServer{
   
      kcpList: make(map[Conv]*kcp.KCP, 128),
   }
}

func (receiver *KcpServer) UdpListen() {
   
  // 创建一个Udp监听 因为Udp客户端和服务器区分不大
  // 所以客户端代码和这个一样
   udp, err := net.ListenUDP("udp", &net.UDPAddr{
   
      IP:   net.ParseIP("0.0.0.0"),
      Port: 8888,
   })
   if err != nil {
   
      // 别问我为啥用Panic
      // 这只是个demo
      panic(err)
   }
   // kcp默认mtu为1400
   data := make([]byte, 1400, 1400)
   for {
   
      // 接收udp数据包
      n, addr, err := udp.ReadFromUDP(data)
      if err != nil {
   
         panic(err)
      }
      fmt.Println(data[:n])
      // kcp包头长度24,且不存在只发个包头的情况
      // 但是 当n==4 的时候代表新链接注册
      // 要是作为客户端的话 可以根据自己的情况来修改
      if n <= 24 && n != 4 {
   
         continue
      }
      // 获取kcp协议头的前4位conv
      conv := binary.LittleEndian.Uint32(data[:4])
      // 看看有没有符合这个conv的kcp连接
      // 要是作为客户端的话 可以根据自己的情况来修改
      k, ok := receiver.kcpList[conv]
      if ok {
   
        // 有已经建立的kcp链接就去做一些设定好的操作
         err := receiver.kcpDo(k, data, n, err)
         if err != nil {
   
            log.Println(err.Error())
         }
         continue
      }
      // 没有就新建一个
      k, conv = receiver.CreateKcp(func(buf []byte) {
   
         _, err := udp.WriteToUDP(buf, addr)
         if err != nil {
   
            panic(err)
         }
      })
      receiver<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值