学习笔记_UDP的可靠传输协议QUIC
UDP可靠性设计,解决的是数据实时性问题,流速问题
如何做到可靠性传输
- ACK机制
确保对方应答 - 重传机制
定时机制,重传策略 - 序号机制 3 2 1 ->2 3 1
对包进行编号 - 重排机制 2 3 1 -> 3 2 1
根据包的序号进行重排,不是当前的先进行缓存 - 窗口机制
发送数据给对方,当时对方一直收,导致我的发送缓存区满了
UDP和TCP的区别
UDP每一次必须一次读完,没读完的就丢掉了
选项 | UDP | TCP |
---|---|---|
是否连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输,使用流量控制和拥塞控制 |
连接对象个数 | 支持一对一,一对多,多对一和多,对多交互通信 | 只能是一对一通信 |
传输方式 | 面向报文 | 面向字节流 |
首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 |
适用场景 | 适用于实时应用(IP电话、视频会议、直播等)游戏行业、物联网行业 | 适用于要求可靠传输的应用,例如文件传输 |
UDP和TCP的格式对比
|16位端口号|16位目标端口|
|16长度|16位校验和|
|数据|
滑动窗口
滑动窗口 发送方和接受方都会维护一个数据帧的序列,这个序列被称作窗口,发送方的窗口大小由接收方确定。目的在于控制发送速度,避免接收方缓存不够大,导致溢出,同时控制流量也可以避免网络拥塞。协议中规定,对于窗口内未经确认的分组需要重传。
- 接收数据的为接收窗口,发送数据的为拥塞窗口(发送窗口),指出窗口大小的通知称为窗口通告
- 窗口大小通过某种算法动态调整
- 窗口增大会消耗内存,其大小需要根据网络环境以及发送方的拥塞窗口来动态调整。
- 一般情况下接收窗口>=发送窗口,发送窗口通过接收窗口动态进行调整
ARQ协议(Automatic Repeat-reQuest)
主要是以下三种模式:
-
即停等式(stop-and-wait)ARQ
发送方发送数据包,然后等待接收方回复ACK并且开始及时
再等待ack的过程中不会发送新的数据包
当数据包没有成功被接收方接受,接收方不会回复ack,发送方等待到超时时间后重新发送数包
反复以上步骤直到收到ack -
回退n帧(go-back-n)ARQ -->GBN协议
回退N步(Go-Back-N,GBN) 回退N步协议允许发送方在等待超时的间歇,可以继续发送分
组。所有发送的分组,都带有序号。在GBN协议中,发送方需响应以下三种事件:
1、上层的调用。上层调用相应send()时,发送方首先要检查发送窗口是否已满。
2、接收ACK。在该协议中,对序号为n的分组的确认采取累积确认的方式,表明接收方已正确接收到序号n以前(包括n)的所有分组。
3、超时。若出现超时,发送方将重传所有已发出但还未被确认的分组
对于接收方来说,若一个序号为n的分组被正确接收,并且按序,则接收方会为该分组返回一个ACK给发送方,并将该分组中的数据交付给上层。在其他情况下,接收方都会丢弃分组。若分组n已接收并交付,那么所有序号比n小的分组也已完成了交付。因此GBN采用累积确认是一个很自然的选择。发送方在发完一个窗口里的所有分组后,会检查最大的有效确认,然后从最大有效确认的后一个分组开始重传。
注意 若序号2的分组丢失,则2和之后的分组都将重传 -
选择性重传(selective repeat)ARQ,SR协议
在SR协议下,发送方需响应以下三种事件:
1、从上层收到数据。当从上层收到数据后,发送方需检查下一个可用于该分组的序号。若序号在窗口中则将数据发送。
2、接收ACK。若收到ACK,且该分组在窗口内,则发送方将那个被确认的分组标记为已接收。若该分组序号等于基序号,则窗口序号向前移动到具有最小序号的未确认分组处。若窗口移动后并且有序号落在窗口内的未发送分组,则发送这些分组。
3、超时。若出现超时,发送方将重传已发出但还未确认的分组。与GBN不同的是,SR协议中的每个分组都有独立的计时器在SR协议下,接收方需响应以下三种事件:
(假设接收窗口的基序号为4,分组长度也为4)
1、序号在[4,7]内的分组被正确接收。该情况下,收到的分组落在接收方的窗口内,一个ACK将发送给发送方。若该分组是以前没收到的分组,则被缓存。若该分组的序号等于基序号4,则该分组以及以前缓存的序号连续的分组都交付给上层,然后,接收窗口将向前移动。
2、序号在[0,3]内的分组被正确接收。在该情况下,必须产生一个ACK,尽管该分组是接收方以前已确认过的分组。若接收方不确认该分组,发送方窗口将不能向前移动。
3、其他情况。忽略该分组对于接收方来说,若一个分组正确接收而不管其是否按序,则接收方会为该分组返回一个ACK给发送方。失序的分组将被缓存,直到所有丢失的分组都被收到,这时才可以将一批分组按序交付给上层。
RTT和RTO
RTO(Retransmission TimeOut)即重传超时时间。如果RTT=200ms,则重传时间为400ms
RTT(Round-Trip Time): 往返时延。表示从发送端发送数据开始,到发送端收到来自接收端的确认(接收端收到数据后便立即发送确认),总共经历的时延。
◼ 链路的传播时间(propagation delay)
◼ 末端系统的处理时间、
◼ 路由器缓存中的排队和处理时间(queuing delay)
流量控制————与接收方的缓存状态相关联
流量控制————对发送方发送速率的控制
- 双方通信,如果发送方的速率大于接收方,接收方处理不来会将数据放到缓存中
- 如果缓存满了,发送方还在发送数据,接受方只能将数据包丢掉,所以需要控制发送速率,让两者处于动态平衡
流量控制————如何控制?发送方根据接收方的窗口大小控制自己的发送窗口大小
接收方每次收到数据包后,再返回确认报文得同时告诉发送方自己得缓存区还剩余多少空闲,我们也把缓存区得剩余大小称之为接收窗口大小,用变量win来表示接收窗口的大小。
发送方收到之后,便会调整自己的发送速率,也就是调整自己发送窗口的大小,当发送方收到接收窗口的大小为0时,发送方就会停止发送数据,防止出现大量丢包情况的发生。
流量控制————发送方何时再继续发送数据?
- 当接收方处理好数据后,接收窗口win>0,接收方发个通知报文去通知发送方
- 当发送方收到接受窗口 win = 0 时,这时发送方停止发送报文,并且同时开启一个定时器,每隔一段时间就发个测试报文去询问接收方,打听是否可以继续发送数据了,如果可以,接收方就告诉他此时接受窗口的大小;如果接受窗口大小还是为0,则发送方再次刷新启动定时器。
拥塞控制————与网络的拥堵情况相关联
为什么需要?
A发送给B数据,A却没有手动B的ACK,而造成这个的原因却是因为太多主机正在使用信道资源,导致网络拥塞了,而A发送的数据被堵在路上没有到达B。但这个时候A却以为丢包了然后进行重传,这样子只会让网络更加拥塞。
拥塞窗口探测过程
A和B建立连接,刚开始A并不知道网络的拥塞情况,需要进行探测,而我们将A一次性能够发送多少个数据包称之为 拥塞窗口
探测过程 通过不断尝试发送数量递增的包,直到出现超时。设置一个阈值,如8,刚开始指数增长1,2,4,8,之后再进行线性增长9,10,11,12…,假如增加到14后出现超时事件,则将14称为 瓶颈值。之后回到最初状态,并设置阈值为瓶颈值/2=14/2=7,然后继续
超时不一定是网络拥塞,所以还需要通过冗余ack来辨别
超时也可以是包丢失或损坏
为了防止这种情况,我们是通过冗余ACK来处理的。我们都知道,数据包是有序号的,如果A给B发送M1, M2, M3, M4, M5…N个数据包,如果B收到了M1, M2, M4…却始终没有收到M3,这个时候就会重复确认M2,意在告诉A,M3还没收到,可能是丢失了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5pEjPXbn-1644536379251)(./resource/image/tcp_reno.png)]
当A连续收到了三个确认M2的ACK,且M3超时事件还没发生。A就知道M3可能丢失了,这个时候A就不必等待M3设置的计时器到期了,而是 快速重传M3。并且把ssthresh设置为MAX的一半,即ssthresh = MAX/2,但是这个时候并非把控制窗口N设置为1,而是让N = ssthresh,N在一个一个增长。
慢启动和快恢复
KCP
TCP是为流量设计的(每秒内可以传输多少KB的数据),讲究的是充分利用带宽。而 KCP是为流速设计的(单个数据包从一端发送到一端需要多少时间),以10%-20%带宽浪费的代价换取了比 TCP快30%-40%的传输速度。TCP信道是一条流速很慢,但每秒流量很大的大运河,而KCP是水流湍急的小激流。KCP有正常模式和快速模式两种,通过以下策略达到提高流速的结果:
RTO翻倍vs不翻倍:(Retransmission TimeOut)即重传超时时间
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都能流畅传输的效果。
名词说明
用户数据:应用层发送的数据,如一张图片2Kb的数据
MTU:最大传输单元。即每次发送的最大数据
RTO:Retransmission TimeOut,重传超时时间。
cwnd:congestion window,拥塞窗口,表示发送方可发送多少个KCP数据包。与接收方窗口有关,与网络状况(拥塞控制)有关,与发送窗口大小有关。
rwnd:receiver window,接收方窗口大小,表示接收方还可接收多少个KCP数据包
snd_queue:待发送KCP数据包队列,被snd_nxt取到后释放
snd_nxt:下一个即将发送的kcp数据包序列号,收到ack后释放
snd_una:下一个待确认的序列号
recv_buf:最先收到数据
recv_queue:靠近用户,将recv_buf中排行徐的放到这里
使用方式
- 创建 KCP对象:ikcpcb *kcp = ikcp_create(conv, user);
- 设置传输回调函数(如UDP的send函数):kcp->output = udp_output;
- 真正发送数据需要调用sendto
- 循环调用 update:ikcp_update(kcp, millisec);
- 输入一个应用层数据包(如UDP收到的数据包):
ikcp_input(kcp,received_udp_packet,received_udp_size);- 我们要使用recvfrom接收,然后扔到kcp里面做解析
- 发送数据:ikcp_send(kcp1, buffer, 8); 用户层接口
- 接收数据:hr = ikcp_recv(kcp2, buffer, 10);
发送窗口和接收窗口
- 发送窗口有最大值,默认是32个分片(IKCPSEG)
- 发送窗口又是变动的
命令
IKCP_CMD_PUSH:普通用户数据包
IKCP_CMD_ACK:应答分片
IKCP_CMD_WASK:窗口询问
IKCP_CMD_WINS:回复窗口大小
用户数据
一个UDP包中有一个或多个segment,segment包括header(24)+Data(…),kcp内部将用户数据划分为多个segment,再把segment封装到udp报文中
每个ack都是独立的segment
自己编写回调函数
发送数据到内核
从内核接收数据
QUIC协议,Quick UDP Internet Connections,谷歌发明的新传输协议
- 主要目的 为了整合tcp协议的可靠性和udp协议的速度和效率。
- QUIC可以在1到2个数据包内完成连接的建立(取决于连接的服务器是新的还是已知的),包括TLS
- QUIC 非常类似于在 UDP 上实现的 TCP + TLS + HTTP/2
- 是在用户态基于udp实现的传输层
- 为什么不再内核统一级别quic、tcp、udp?
- quic还是一个草案,还在更新
- 放在内核影响大,所有系统都要更新
- 路由器,还有很多中间设备、防火墙也要改动
1)小地方,路由封杀UDP 443端口( 这正是QUIC 部署的端口);
2)UDP包过多,由于QS限定,会被服务商误认为是攻击,UDP包被丢弃;
3)无论是路由器还是防火墙目前对QUIC都还没有做好准备。
- QUIC 与现有 TCP + TLS + HTTP/2 方案相比,有以下几点主要特征:
- 利用缓存,显著减少连接建立时间;
- 改善拥塞控制,拥塞控制从内核空间到用户空间;
- 没有 head of line 阻塞的多路复用;
- 前向纠错,减少重传;
- 连接平滑迁移,网络状态的变更不会影响连接断线。