当前位置: 首页 > news >正文

linux网络编程6——基于UDP的可靠传输协议KCP/QUIC

文章目录

  • 基于UDP的可靠传输协议KCP/QUIC
    • 1 KCP基本原理
      • 1.1 如何做到可靠传输
      • 1.2 TCP和UDP如何选择
      • 1.3 ARQ协议
        • 1.3.1 停止等待ARQ
        • 1.3.2 回退nARQ
        • 1.3.3 选择重传ARQ
      • 1.4 RTT和RTO
      • 1.5 流量控制——滑动窗口
      • 1.6 拥塞控制
      • 1.7 KCP协议的优势
        • 1.7.1 RTO翻倍 vs 不翻倍
        • 1.7.2 选择重传 vs 全部重传
        • 1.7.3 快速重传
        • 1.7.4 延迟ACK vs 非延迟ACK
        • 1.7.5 UNA vs ACK + UNA
        • 1.7.6 非退让流控
    • 2. KCP源码分析和使用
      • 2.1 名词说明
      • 2.2 kcp协议头
        • 2.2.1 ikcp中的主要数据结构
      • 2.2 KCP的使用方式
        • 2.2.1 生成会话ID
        • 2.2.2 流程
        • 2.2.3 KCP配置模式
    • 3. 重点问题
    • 4. QUIC简介
      • 4.1 QUIC 的核心特点
    • 学习参考

基于UDP的可靠传输协议KCP/QUIC

本文详细介绍了KCP协议基本原理,并简要介绍了KCP的使用方式以及QUIC协议。

1 KCP基本原理

1.1 如何做到可靠传输

可靠传输最主要是依赖于ARQ协议,即自动重复请求协议,它的基本功能是在丢包时请求重传,它的如下机制保证了可靠的传输:

  • ACK确认机制,确保数据已被接收。

  • 重传机制,当超时或者判断丢包时重传数据。

  • 序号机制,检测是否有数据丢失和是否有序。

  • 重排机制,使乱序到达的数据重新有序。

1.2 TCP和UDP如何选择

以下是一个总结UDP和TCP之间主要区别的表格:

特性TCPUDP
连接方式面向连接(建立连接)无连接(无需建立连接)
传输方式面向字节流面向报文
可靠性提供可靠的数据传输(数据包顺序、重传机制)不保证可靠性(可能丢失数据包)
数据顺序确保数据包按顺序到达不保证数据包顺序
流控制提供流控制(使用滑动窗口协议)不提供流控制
拥塞控制提供拥塞控制机制不提供拥塞控制
适用场景适合需要可靠传输的应用(如网页、文件传输)适合实时应用(如视频、语音);游戏行业
传输速率相对较慢(由于连接管理、错误校正)相对较快(无连接管理、简单)
应用协议HTTP, FTP, SMTP 等DNS, DHCP, VoIP 等

字节流: 连续、有序。

报文:离散,无序。

1.3 ARQ协议

ARQ(Automatic Repeat reQuest),即自动重复请求,是一种确保可靠传输的机制。可以参考这篇博文详细了解。

1.3.1 停止等待ARQ

很少被采用。

1.3.2 回退nARQ

TCP采用这种重传机制。ARQ

1.3.3 选择重传ARQ

KCP采用这种方式。

1.4 RTT和RTO

RTO(Retransmission TimeOut)即重传超时时间

RTT(Round-Trip Time)即往返时延

1.5 流量控制——滑动窗口

为什么要进行流量控制:发送方的发送速率与接收方的接受速率存在差异

  1. 如果发送方速率大于接收方速率,接收方就不得不丢弃很多数据包,导致传输效率下降。
  2. 如果发送方速率小于接收方速率,会浪费带宽。

怎么进行流量控制:滑动窗口

接收方会告诉发送方自己的接收窗口的大小,还能够接收多少数据,这样发送方知道能够发送多少数据。

其他小问题总结

  • 接收窗口大小固定吗?不固定,需要根据网络情况动态调整。
  • 接收窗口越大越好吗?不是,接收窗口过大容易导致乱序和丢包。
  • 发送窗口和接收窗口相等吗?一般接收窗口>=发送窗口。

1.6 拥塞控制

主要由四个算法组成:

  • 慢启动
  • 拥塞避免
  • 快速恢复 (TCP Reno版本开始使用)
  • 快速重传

简要了解可以参考我的博文,详细了解可以参考这篇博文。

1.7 KCP协议的优势

在不稳定的网络中,KCP以10%-20%带宽浪费的代价,换取比TCP快30%-40%的传输速度

在网络通畅的情况下,文件传输速度上,kcp < tcp。

KCP的传输速度优势本质上来自于其实现的ARQ协议的重传策略。它通过以下机制实现了这样的目标:

  • 尽量减少重传超时等待的时间(即RTO)。
  • 尽量减少丢包的成本(得益于选择重传ARQ)。

尽管实现这样的目标是以消耗的网络带宽增加为代价的。

1.7.1 RTO翻倍 vs 不翻倍

TCP的超时时长计算策略是翻倍,而KCP启动快速模式后是乘以1.5,提高了传输速度。

KCP还可以定制重传策略定制丢包策略

1.7.2 选择重传 vs 全部重传

KCP使用选择拒绝自动重复请求,只重传那些已经丢失的包。

KCP使用快速重传,KCP的快速重传是指当发现某个数据段被跳过确认多次后,不必等待RTO而直接重传,大大改善了丢包时的重传速度。

1.7.3 快速重传

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

1.7.4 延迟ACK vs 非延迟ACK

TCP为了充分利用带宽,选择延迟发送ACK,这样超时计算会算出较大的RTT时间,从而延长了RTO时间。而KCP的ACK是否延迟可以调节。

在TCP中,一个ACK可以确认多个数据段,因此采用延迟ACK可以减少网络包数量。

1.7.5 UNA vs ACK + UNA

ARQ的响应模式有两种,UNA(Unacknowledged Number Acknowledged)和ACK,UNA模式下确认序号指的是下一个期待收到的序号,ACK模式下确认序号指的是已经收到的数据段的序号。只用UNA会导致太多重传,只用ACK会导致丢失成本太高(ACK包丢失可能会导致不必要的重传)。在KCP协议中,除去单独的ACK包外,所有的包都有UNA信息。

1.7.6 非退让流控

KCP正常模式下通TCP一样使用公平退让法则,即发送窗口大小由:发送缓存大小、接收端窗口大小、丢包退让、拥塞窗口这四个因素决定。当采用非退让流控时,就不考虑后两个因素。

2. KCP源码分析和使用

2.1 名词说明

MTU: 最大传输单元,是数据链路层的概念,以太网为1500字节。实际在传输层考虑到PDU的消耗,使用1400字节比较合适。

cwnd:拥塞窗口大小

rwnd:接收方窗口大小

snd_queue:待发送KCP数据包的队列

snd_buf:发送缓冲区

snd_nxt:下一个即将发送的kcp数据包序列号

snd_una:下一个待确认的序列号

2.2 kcp协议头

0                            4               6           8  (BYTE)
+----------------------------+---------------+-----------+
|            conv            | cmd   |  frg  |    wnd    | 8   
+----------------------------+---------------+-----------+
|            ts              |            sn             | 16
+----------------------------+---------------------------+
|            una             |            len            | 24
+----------------------------+---------------------------+
|                        DATA (optional)                 |
+--------------------------------------------------------+
  • conv: 会话标识
  • cmd: 命令,如IKCP_CMD_ACK
  • frg: 分片标识
  • wnd: 接收窗口大小
  • ts: 时间序列
  • sn: 序列号
  • una: 下一个期待的数据序列号
  • len: 数据长度
  • data: 数据
2.2.1 ikcp中的主要数据结构
  • ikcp控制块,类似与tcp中的tcp控制块,保存每个kcp会话的核心数据。
struct IKCPCB
{/* 会话状态信息 */IUINT32 conv;   // 标识会话IUINT32 mtu;    // 最大传输单元,默认数据为1400,最小为50IUINT32 mss;    // 最大分片大小,不大于mtuIUINT32 state;  // 连接状态(0xffffffff表示断开连接)int nocwnd;     // 取消拥塞控制int stream;     // 是否采用流传输模式int logmask;    // 日志的类型,如IKCP_LOG_IN_DATA,方便调试/* 用于ARQ的字段 */IUINT32 snd_una;    // 第一个未确认的包IUINT32 snd_nxt;    // 下一个待分配包的序号IUINT32 rcv_nxt;	// 待接收消息序号IUINT32 ts_recent;	// 最近收到的数据的时间  IUINT32 ts_lastack;	// 上一个收到的ACK的时间IINT32  rx_rttval;      // RTT的变化量,代表连接的抖动情况IINT32  rx_srtt;        // smoothed round trip time,平滑后的RTT;IINT32  rx_rto;         // 收ACK接收延迟计算出来的重传超时时间IINT32  rx_minrto;      // 最小重传超时时间IUINT32 *acklist;   //待发送的ack的列表。当收到一个数据报文时,将其对应的ACK报文的 sn 号以及时间戳 ts 同时加入到acklist 中,即形成如 [sn1, ts1, sn2, ts2 …] 的列表/* 滑动窗口 */struct IQUEUEHEAD snd_queue;    //发送消息的队列 struct IQUEUEHEAD rcv_queue;    //接收消息的队列, 是已经确认可以供用户读取的数据struct IQUEUEHEAD snd_buf;      //发送消息的缓存 和snd_queue有什么区别struct IQUEUEHEAD rcv_buf;      //接收消息的缓存, 还不能直接供用户读取的数据/* 拥塞控制 */IUINT32 ssthresh;       // 拥塞窗口的阈值IUINT32 cwnd;           // 拥塞窗口大小, 动态变化...;int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user);//发送消息的回调函数void (*writelog)(const char *log, struct IKCPCB *kcp, void *user);  // 写日志的回调函数
}
  • ikcp每个分片的数据结构
struct IKCPSEG
{struct IQUEUEHEAD node;IUINT32 conv;   // 会话编号,和TCP的con一样,确保双方需保证conv相同,相互的数据包才能被接收.conv唯一标识一个会话IUINT32 cmd;    // 区分不同的分片.IKCP_CMD_PUSH数据分片;IKCP_CMD_ACK:ack分片;IKCP_CMD_WASK:请求告知窗口大小;IKCP_CMD_WINS:告知窗口大小IUINT32 frg;    // 标识segment分片ID,用户数据可能被分成多个kcp包发送, 为0时代表   IUINT32 wnd;    // 剩余接收窗口大小(接收窗口大小-接收队列大小),发送方的发送窗口不能超过接收方给出的数值IUINT32 ts;     // 发送时刻的时间戳IUINT32 sn;     // 分片segment的序号,按1累加递增IUINT32 una;    // 待接收消息序号(接收滑动窗口左侧).对于未丢包的网络来说,una是下一个可接收的序号,如收到sn=10的包,una为11IUINT32 len;    // 数据长度IUINT32 resendts;   // 下次超时重传时间戳IUINT32 rto;        //该分片的超时等待时间,其计算方法同TCPIUINT32 fastack;    // 收到ack时计算该分片被跳过的累计次数,此字段用于快速重传,自定义需要几次确认开始快速重传IUINT32 xmit;       // 发送分片的次数,每发一次加1.发送的次数对RTO的计算有影响,但是比TCP来说,影响会小一些.char data[1];
};

2.2 KCP的使用方式

2.2.1 生成会话ID

会话ID用来标识客户端与服务器端的一条逻辑连接。

两种方式:

  • 客户端使用随机数产生uuid。
  • 服务器端产生唯一id然后通过http协议等传给客户端。

ikcp中的实现:

// read conv 获取会话id
IUINT32 ikcp_getconv(const void *ptr)
{IUINT32 conv;ikcp_decode32u((const char*)ptr, &conv);return conv;
}/* decode 32 bits unsigned int (lsb) */
static inline const char *ikcp_decode32u(const char *p, IUINT32 *l)
{
#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN*l = *(const unsigned char*)(p + 3);*l = *(const unsigned char*)(p + 2) + (*l << 8);*l = *(const unsigned char*)(p + 1) + (*l << 8);*l = *(const unsigned char*)(p + 0) + (*l << 8);
#else memcpy(l, p, 4);
#endifp += 4;return p;
}

每个会话都对应一个kcp控制块,会话在构造时也会设置ikcpcb中的output回调函数,这样只需要封装号session类,用户就只需要与session打交道了。

2.2.2 流程
  • 创建KCP对象
ikcpcb *kcp = ikcp_create(conv, user);
  • 设置发送回调函数(如UDP的send函数)
kcp->output = udp_output;
  • 循环调用update
ikcp_update(kcp, millisec);	// 在一个线程、定时器5ms/10ms做调度
  • 输入一个应用层数据包(如UDP收到的数据包)
ikcp_input(kcp, received_udp_packet, received_udp, size);
  • 发送数据
ikcp_send(kcp1, buffer, 8);
  • 接收数据
hr = ikcp_recv(kcp2, buffer, 10);

需要注意,接收数据时需要用户先用UDP socket的API读取出数据,然后调用ikcp_input(),然后再调用ikcp_recv()。

2.2.3 KCP配置模式

工作模式

int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc)
  • nodelay: 是否开启ACK延迟确认,0不启用,1启用
  • interval: 协议内部工作工作的interval,单位ms,默认10ms
  • resend:是否开启快速重传模式,默认不开启
  • nc:是否关闭流控,默认不关闭

最大窗口

int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);	// 默认32

最大传输单元:

int ikcp_setmtu(ikcpcb *kcp, int mtu);	// 默认1400

最小RTO:

kcp->rx_minrto = 10;	// 快速模式下为30ms,可以手动更改

3. 重点问题

  1. tcp为什么可靠?

tcp的可靠性来自于滑动窗口和ARQ协议,它们保证了数据不丢失、不乱序。

  1. kcp为什么牺牲带宽换取速度?

kcp适用于对实时通讯要求较高的场景,例如直播、网络游戏等领域。它基于UDP协议,省去了三次握手的过程。kcp使用了自定义重传机制(如RTO、快速重传)、自定义是否启用拥塞控制,选择决绝ARQ也降低了重传成本。因此综合来看,kcp增加了网络中数据包的数量,但是提高了数据包的实时性。在不稳定的网络环境下,kcp的优势更为明显。

  1. udp怎么实现客户端和服务端编程?服务端怎么维护和客户端的逻辑连接?

UDP不能像tcp那样建立连接并长时间维持上下文信息,而只能通过每个数据包来源的目的地址和端口识别对方。为了维护逻辑连接,通常服务端可以使用一个具有唯一ID的会话保存特定客户端的信息。这个会话机制包括:

  • 客户端标识
  • 状态管理
  • 心跳机制

4. QUIC简介

QUIC(Quick UDP Internet Connections)是一种基于 UDP 的传输层协议,由 Google 开发,旨在为网络通信提供更快、更可靠的体验。QUIC 的设计目标是在保持低延迟的同时,提供与 TCP 相似的可靠性和拥塞控制,并解决一些传统 TCP 和 TLS 协议的缺点,比如慢启动、连接延迟高等问题。

4.1 QUIC 的核心特点

  • 低延迟连接建立

QUIC 通过将握手和加密合并到一个过程,使客户端和服务端能够在一次往返(1-RTT)中完成握手。对于已经建立过连接的客户端,QUIC 还支持 0-RTT 握手,这意味着客户端可以在发送初始请求的同时发送数据,极大地减少了连接延迟。

  • 集成的 TLS 加密

QUIC 将 TLS 1.3 协议集成在其协议栈中,从而在连接开始时即提供加密通信。这种方式不仅能提高连接安全性,还避免了传统 TCP 和 TLS 分别握手带来的延迟。

  • 多路复用

传统 TCP 实现 HTTP/2 的多路复用时,存在着“队头阻塞”问题(某个流的丢包会阻塞其他流的数据传输)。QUIC 通过流的独立处理机制,使每个流都可以独立地进行数据传输,避免了队头阻塞的情况。

  • 灵活的拥塞控制

QUIC 的拥塞控制算法可由实现方选择或配置,这使得它更具灵活性,可以根据网络情况灵活调整,进一步提高传输效率和稳定性。此外,QUIC 的流量控制机制可以控制流级别的数据量,以防止客户端或服务端被大量数据淹没。

  • 基于 UDP

QUIC 通过 UDP 实现,不受操作系统内核中 TCP 堆栈限制的影响,便于快速更新和改进协议,特别适合现代互联网的需求。

学习参考

学习更多相关知识请参考零声 github。


http://www.mrgr.cn/news/59042.html

相关文章:

  • R语言编程
  • Linux基础命令,文件操作命令(touch,cat,more,cp,mv,rm)
  • 学习threejs,THREE.PointCloud(新版本改名:THREE.Points)批量管理粒子
  • 55. Jump Game
  • 函数属性.
  • Django 序列化serializers
  • Minio文件服务器:安装
  • [LeetCode] 77. 组合
  • shodan1,shodan简介和kali下的使用
  • 【Linux】线程池详解及其基本架构与单例模式实现
  • [LeetCode] 494. 目标和
  • 【动态规划】【简单多状态dp问题】买卖股票相关问题(冷冻期、手续费、限制次数)
  • 基于SSM农业信息管理系统的设计
  • python曲线拟合通用代码
  • 数据结构(java)——数组的构建和插入
  • 【网络安全】一文讲清Zero Trust(零信任)安全
  • 【Python爬虫+数据分析】详细教学知网文献基本信息爬取方式(附详细教程+完整代码)
  • ctfshow的sql注入解题思路171-211
  • 文言编程:古老文字与现代编程的融合
  • 禾川SV-X2E A伺服驱动器参数设置——脉冲型
  • Gateway 统一网关
  • 【论文阅读】ESRGAN
  • C++ string类常用接口总结
  • 「C/C++」C++17 之 std::filesystem::directory_entry 文件系统目录条目
  • sql语句中的Group By 分组查询
  • AI神器,豆包自带抠图,完全免费!路人甲、去水印,轻轻一擦,全去掉