在
SR
协议下,接收方需响应以下三种事件:
(
假设接收窗口的基序号为
4
,分组长度也为
4)
1
、序号在
[4,7]
内的分组被正确接收。该情况下,收到的分组落在接收方的窗口内,一个
ACK
将发送给发送方。若该分组是以前没收到的分组,则被缓存。若该分组的序号等于基序号
4
,
则该分组以及以前缓存的序号连续的分组都交付给上层,然后,接收窗口将向前移动。
2
、序号在
[0,3]
内的分组被正确接收。在该情况下,必须产生一个
ACK
,尽管该分组是接收方
以前已确认过的分组。若接收方不确认该分组,发送方窗口将不能向前移动。
3
、其他情况。忽略该分组
对于接收方来说,若一个分组正确接收而不管其是否按序,则接收方会为该分组返回一个
ACK
给发送方。失序的分组将被缓存,直到所有丢失的分组都被收到,这时才可以将一批分组按
序交付给上层。
4.1 RTT和RTO
RTO
(
Retransmission TimeOut
)即重传超时时间。
◼
RTT(Round-Trip Time)
:
往返时延
。表示从发送端发送数据开始,到发送端收到来自
接收端的确认(接收端收到数据后便立即发送确认),总共经历的时延。
由三部分组成:
◼
链路的传播时间(
propagation delay
)
◼
末端系统的处理时间、
◼
路由器缓存中的排队和处理时间(
queuing delay
)
其中,前两个部分的值对于一个
TCP
连接相对固定,路由器缓存中的排队和处理时间会
随着整个网络拥塞程度的变化而变化。 所以
RTT
的变化
在一定程度上
反应网络的拥塞程
度
。
4.2 流量控制
◼
双方在通信的时候,发送方的速率与接收方的速率是不一定相等,
如果发送方
的发送速率太快,会导致接收方处理不过来
,这时候接收方只能把处理不过来
的数据存在缓存区里(失序的数据包也会被存放在缓存区里)
接收缓存
。
◼
如果缓存区满了发送方还在疯狂着发送数据,接收方只能把收到的数据包丢掉,
大量的丢包会极大着浪费网络资源,
因此,我们需要
控制发送方的发送速率
,
让接收方与发送方处于一种动态平衡才好。
◼
对发送方发送速率的控制,称之为流量控制。
◼
公平使用带宽 100M 10个 10M左右
4.3 如何做到流量控制
◼
接收方每次收到数据包,可以在发送确定报文的时候,同时告诉发送方自己的缓存区还剩余多少
是空闲的,我们也把缓存区的剩余大小称之为接收窗口大小,用变量win来表示接收窗口的大小。
◼
发送方收到之后,便会调整自己的发送速率,也就是调整自己发送窗口的大小,
当发送方收到接
收窗口的大小为0时,
发送方就会停止发送数据
,防止出现大量丢包情况的发生。
4.4 发送方何时再继续发送数据
当发送方停止发送数据后,该怎样才能知道自己可以继续发送数据?
1. 当接收方处理好数据,接受窗口 win > 0
时,
接收方发个通知报文去通知发送方
,告诉他可以继续发 送数据了。当发送方收到窗口大于0
的报文时,就继续发送数据。
2. 当发送方收到接受窗口 win = 0
时,这时发送方停止发送报文,并且同时开启一个定时器,
每一段
时间就发个测试报文去询问接收方
,打听是否可以继续发送数据了,如果可以,接收方就告诉他此时 接受窗口的大小;如果接受窗口大小还是为0
,则发送方再次刷新启动定时器。
4.5 流量控制小结
1. 通信的双方都拥有两个滑动窗口,一个用于接受数据,称之为接收窗口;一个用于
发送数据,称之为拥塞窗口(即发送窗口)。指出接受窗口大小的通知我们称之为窗口
通告。
2. 接收窗口的大小固定吗?
接受窗口的大小是根据某种算法动态调整的。
3. 接收窗口越大越好吗?
当接收窗口达到某个值的时候,再增大的话也不怎么会减少
丢包率的了,而且还会更加消耗内存。所以接收窗口的大小必须根据网络环境以及
发送发的的拥塞窗口来动态调整。
4. 发送窗口和接收窗口相等吗?
接收方在发送确认报文的时候,会告诉发送发自己的
接收窗口大小,而发送方的发送窗口会据此来设置自己的发送窗口,但这并不意味
着他们就会相等。首先接收方把确认报文发出去的那一刻,就已经在一边处理堆在
自己缓存区的数据了,所以一般情况下
接收窗口 >= 发送窗口
。
5 拥塞控制
拥塞控制和流量控制虽然采取的动 作很相似,但
拥塞控制与网络的拥 堵情况相关联
,
而流量控制与接收 方的缓存状态相关联。
6 .1 udp编程模型
6.2 udp并发编程
自己找源码
6.3 udp如何才能做到可靠,kcp协议在哪些方面上有优势
以10%-20%带宽浪费的代价换取了比 TCP快30%-40%的传输速度。
RTO
翻倍
vs
不翻倍:
TCP
超时计算是
RTOx2
,这样连续丢三次包就变成
RTOx8
了
,十分恐怖,而
KCP
启动快
速模式后不
x2
,只是
x1.5
(实验证明
1.5
这个值相对比较好),提高了传输速度。
以
RTO=100ms
为例
选择性重传
vs
全部重传:
TCP
丢包时会全部重传从丢的那个包开始以后的数据,
KCP
是选择性重传,只重传真
正丢失的数据包。
快速重传(
跳过多少个包马上重传
)(如果使用了快速重传,可以不考虑
RTO
)):
发送端发送了
1,2,3,4,5
几个包,然后收到远端的
ACK: 1,
3, 4, 5
,当收到
ACK3
时,
KCP
知道
2
被跳过
1
次,收到
ACK4
时,知道
2
被跳过了
2
次,此时可以认为
2
号丢失,不用
等超时,直接重传
2
号包,大大改善了丢包时的传输速度。
fastresend =2
6.4 UDP如何可靠,KCP协议在哪些方面有优势2
以10%-20%带宽浪费的代价换取了比 TCP快30%-40%的传输速度。
延迟
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
都能流畅传输的效果。
7.1 牛逼的KCP协议
◼
kcp
官方:
https://github.com/skywind3000/kcp
◼
名词说明
用户数据:应用层发送的数据,如一张图片
2Kb
的数据
MTU
:最大传输单元。即每次发送的最大数据,
1500
实际使用
1400
RTO
:
Retransmission TimeOut
,重传超时时间。
cwnd: congestion window
,拥塞窗口,表示发送方可发送多少个
KCP
数据包。与接
收方窗口有关,与网络状况(拥塞控制)有关,与发送窗口大小有关。
rwnd: receiver window,
接收方窗口大小,表示接收方还可接收多少个
KCP
数据包
snd_queue:
待发送
KCP
数据包队列
snd_buf:
snd_nxt:
下一个即将发送的
kcp
数据包序列号
snd_una:
下一个待确认的序列号,即是之前的包接收端都已经收到。
7.2 如何使用KCP协议
1. 创建 KCP
对象:
ikcpcb *kcp = ikcp_create(conv, user);
2. 设置发送回调函数(如UDP
的
send
函数):
kcp->output = udp_output;
1. 真正发送数据需要调用sendto
3. 循环调用 update
:
ikcp_update(kcp, millisec); //
在一个线程、定时器
5ms/10m
做调 度
4. 输入一个应用层数据包(如UDP收到的数据包)
: ikcp_input(kcp,received_udp_packet,received_udp_size);
1. 我们要使用recvfrom接收,然后扔到kcp里面做解析
5. 发送数据:
ikcp_send
(kcp1, buffer, 8); 用户层接口
6. 接收数据:hr =
ikcp_recv(
kcp2, buffer, 10); 用户层读取数据
7.3 KCP源码流程图

7.4 KCP的配置方式
1.
工作模式:
int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc)
nodelay
:是否启用
nodelay
模式,
0
不启用;
1
启用。
interval
:协议内部工作的
interval
,单位毫秒,比如
10ms
或者
20ms
resend
:快速重传模式,默认
0
关闭,可以设置
2
(
2
次
ACK
跨越将会直接重传)
nc
:是否关闭流控,默认是
0
代表不关闭,
1
代表关闭。
默认模式:
ikcp_nodelay(kcp, 0, 10, 0, 0);
普通模式:
ikcp_nodelay(kcp, 0, 10, 0, 1);
关闭流控等
极速模式:
ikcp_nodelay(kcp, 2, 10, 2, 1)
,并且修改
kcp1
->
rx_
minrto
=
10
;
kcp1
->
fastresend
=
1
;
2.
最大窗口:
int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
该调用将会设置协议的最大发送窗口和最大接收窗口大小,默认为
32
,单位为包。
3.
最大传输单元:
int ikcp_setmtu(ikcpcb *kcp, int mtu);
kcp协议并不负责探测
MTU
,默认
mtu
是
1400
字节
4.
最小
RTO
:不管是
TCP
还是
KCP
计算
RTO
时都有
最小
RTO
的限制
,即便计算出来
RTO
为
40ms
,由于默认的
RTO
是
100ms
,协议只有在
100ms
后才能检测到丢包,快速模式下为
30ms
,可以手动更改该值:
kcp->rx_minrto = 10;
7.5 KCP协议头
[0,3]conv:
连接号。
UDP
是无连接的,
conv
用于表示来自于哪个
客户端。对连接的一种替代
[4]cmd:
命令字。如,
IKCP_CMD_ACK
确认命令,
IKCP_CMD_WASK
接收窗口大小询问命令,
IKCP_CMD_WINS
接收
窗口大小告知命令,
[5]frg:
分片,用户数据可能会被分成多个
KCP
包,发送出去
[6,7]wnd:
接收窗口大小,发送方的发送窗口不能超过接收方给
出的数值
[8,11]ts:
时间序列
[12,15]sn:
序列号
[16,19]una:
下一个可接收的序列号。其实就是确认号,收到
sn=10
的包,
una
为
11
[20,23]len
:数据长度
data:
用户数据,这一次发送的数据长度
7.6 KCP发送数据过程



7.7 kcp发送窗口
snd_wnd
:固定大小,默认
32
rmt_wnd:远端接收窗口大小,
默认
32
cwnd
:滑动窗口,可变,越小
一次能发送的数据越小
发送速率的控制是:本质是根据
滑动窗口控制把数据从
snd_
_queue
加入到
send_buf
。
代码案例
// calculate window size 取发送窗口和远端窗口最小值得到拥塞窗口小
cwnd = _imin_(kcp->snd_wnd, kcp->rmt_wnd); // 当rmt_wnd为0的时候,
// 如果做了流控制则取配置拥塞窗口、发送窗口和远端窗口三者最小值
if (kcp->nocwnd == 0) cwnd = _imin_(kcp->cwnd, cwnd); // 进一步控制cwnd大小, kcp->cwnd拥塞窗口
// kcp->nocwnd = 1 少了一次_imin_的判断,可以尽量多发送数据
// move data from snd_queue to snd_buf
// 从snd_queue移动到snd_buf的数量不能超出对方的接收能力 此时如果
// 发送那些符合拥塞范围的数据分片
// kcp->snd_una = 10
// cwnd = 3
//
while (_itimediff(kcp->snd_nxt, kcp->snd_una + cwnd) < 0) {
IKCPSEG *newseg;
if (iqueue_is_empty(&kcp->snd_queue)) break;
// 从snd_queue读取segment
newseg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node);
iqueue_del(&newseg->node);
// 插入到snd buf 发送窗口
iqueue_add_tail(&newseg->node, &kcp->snd_buf); // 从发送队列添加到发送缓存
kcp->nsnd_que--;
kcp->nsnd_buf++;
//设置数据分片的属性
newseg->conv = kcp->conv;
newseg->cmd = IKCP_CMD_PUSH;
newseg->wnd = seg.wnd; // 告知对方当前的接收窗口
newseg->ts = current; // 当前时间
newseg->sn = kcp->snd_nxt++; // 序号
newseg->una = kcp->rcv_nxt; // 告诉对方可以发送的下一个包序号
newseg->resendts = current; // 当前发送的时间
newseg->rto = kcp->rx_rto; // 超时重传的时间, 重传间隔的时间
newseg->fastack = 0; // 是否快速重传
newseg->xmit = 0; // 重传次数
}
7.8 kcp 接收数据过程
7.9接收窗口
snd_wnd
:固定大小,默认
32
rmt_wnd
:远端接收窗口大小,默认
32
cwnd
:滑动窗口,可变,越小一次能
发送的数据越小
接收窗口的控制是:
recv_queue
的接
收能力,比如默认接收端口为
32
,如
果
recv_queue
接收了
32
个包后则接收
窗口为
0
,然后用户读走了
32
个包,
则接收窗口变为
32
。
IKCP_CMD_PUSH
命令
7.10KCP确认包处理流程
7.11 kcp快速确认

7.12 ikcp_inp

7.13 应答列表acklist
◼
收到包后将序号,时间戳存储到应答列表。
在
ikcp_input
函数调用
ikcp_ack_push
存储应答包
ikcp_ack_push
(
kcp
,
sn
,
ts
);
//
对该报文的确认
ACK
报文放入
ACK
列表中
◼
发送应答包
在
ikcp_flush
函数发送应答包
◼
应答包解析
在
ikcp_input
函数进行解析,判断
IKCP_CMD_ACK
7.14 流量控制和拥塞控制
RTO
计算(与
TCP
完全一样)
RTT
:一个报文段发送出去,到收到对应确认包的时间差。
SRTT(kcp->rx_srtt)
:
RTT
的一个加权
RTT
平均值,平滑值。
RTTVAR(kcp->rx_rttval)
:
RTT
的平均偏差,用来衡量
RTT
的抖动。
多个
rtt
取平均
以单个的值
RTT 100
RTT 60
RTT 80
比如三个
RTT
平均
RTT=80
研究
tcp
协议栈
RTT
计算方法
7.15 探测对方接收窗口
ikcp_flush
发送探测窗口IKCP_CMD_WASK
ikcp_input
函数
cmd == IKCP_CMD_WASK
,标记
kcp->probe |= IKCP_ASK_TELL;
ikcp_flush
回应探测
IKCP_CMD_WINS
三 在项目中集成KCP协议栈!
1. 范例演示
2 如何集成到项目中,参考代码的chat_server.cc和chat_client.cc两个代码实现聊天室功能
3. 如何集成到项目中,参考asio_kcp


四 QUIC协议
1.QUIC前世今生
◼
QUIC ,即 快速UDP网络连接 ( Quick UDP Internet Connections ), 是由
Google 提出的实验性网络传输协议 ,位于 OSI 模型传输层。 QUIC 旨在解决
TCP 协议的缺陷,并最终替代 TCP 协议, 以减少数据传输,降低连接建立延
迟时间,加快网页传输速度。
◼
QUIC诞生于2012年
2.QUIC为什么在应用层实现
◼
新的传输层协议通常会经过严格的设计
,分析和评估可重复的结果,证明候选协议对
现有协议的正确性和公平性,开发新的传输层协议和它在操作系统进行广泛部署之间
通常需要花费数年的时间。
◼
再者,用户与服务器之间要经过许多防火墙、
NAT
(地址转换)、路由器和其他中间设
备,
这些设备很多只认
TCP
和
UDP
。如果使用另一种传输层协议,那么就会有可能无法
建立连接或者报文无法转发,这些中间设备会认为除
TCP
和
UDP
协议以外的协议都是不
安全或者有问题的。
3.QUIC协议术语
◼
QUIC
连接
:
Client
和
Server
之间的通信关心,
Client
发起连接,
Server
接受连接
◼
流(
Stream
)
:一个
QUIC
连接内,单向或者双向的有序字节流。一个
QUIC
连
接可以同时包含多个
Stream
◼
帧(
Frame
)
:
QUIC
连接内的最小通信单元。一个
QUIC
数据包(
packet
)中的
数据部分包含一个或多个帧
4.QUIC和TCP对比

5.QUIC报文格式

6.QUIC报文格式-Stream帧1

7.QUIC报文格式-Stream帧2

8.QUIC的特点
1. 连接建立低时延
2. 多路复用
3. 无队头阻塞
4. 灵活的拥塞控制机制
5. 连接迁移
6. 数据包头和包内数据的身份认证和加密
7. FEC前向纠错
8. 可靠性传输
9. 其他
9.省略过多内容......................
10.总结
1.
路由封杀
UDP 443
端口(这正是
QUIC
部署的端口)
2. UDP
包过多,由于
QoS
(
Quality of Service
,服务质量)限定,会被服务器商认
为是攻击,
UDP
包被丢弃
3.
无论是路由器还是防火墙目前对
QUIC
都还没做好准备
4. QUIC
有较多的开源库,标准尚未真正落地,对应的开源库没有经过充分的验证,
不建议中小厂商在
QUIC
上完全跟进
。