TCP可靠传输及流量控制系列五:TCP流量控制基本算法
TCP流量控制基本算法
一、交互式数据流Nagle算法
在一个Rlogin连接上客户一般每次发送一个字节到服务器,这就产生了一些41字节长的分组:20字节的IP首部、20字节的TCP首部和1个字节的数据。在局域网上这些小分组通常不会引起麻烦,因为局域网一般不会出现拥塞。但在广域网上,这些小分组则会增加拥塞出现的可能性。
一个简单有效的方法就是采用RFC 896中所建议的Nagle算法。
Nagle算法:在一个TCP连接上最多只能有一个未被确认的未完成的分组,在该分组的确认到达之前不能发送其他的小分组。相反,TCP收集这些少量的分组,并在确认到来时以一个分组的方式发送出去。
算法优点:它是自适应的,确认到达的越快,数据也就发送的越快。
但有时也必须关闭Nagle算法,一个典型的例子是X窗口系统服务器。小消息(鼠标移动)必须无时延地发送,以便为进行某种操作的交互用户提供实时的反馈。
二、慢启动与拥塞避免算法
1、算法描述
慢启动算法是在一个连接上发起数据流的方法。
拥塞避免算法是一种处理丢失分组的方法。
拥塞避免算法假定由于分组受到损坏引起的丢失是非常少的(远小于1 %),因此分组丢失就意味着在源主机和目的主机之间的某处网络上发生了拥塞。
有两种分组丢失的指示:发生超时和接收到重复的确认。如果使用超时作为拥塞指示,则需要使用一个好的RT T算法。4.4BSD-Lite采用的是超时作为拥塞指示。
拥塞避免算法和慢启动算法是两个目的不同、独立的算法。但是当拥塞发生时,我们希
望降低分组进入网络的传输速率,于是可以调用慢启动来作到这一点。在实际中这两个算法
通常在一起实现。拥塞避免算法和慢启动算法需要对每个连接维持两个变量:一个拥塞窗口cwnd和一个慢启动门限ssthresh。这样得到的算法的工作过程如下:
1) 对一个给定的连接,初始化cwnd为1个报文段,ssthresh为65535个字节。
2) TCP输出例程的输出不能超过cwnd和接收方通告的窗口的大小。拥塞避免是发送方使用的流量控制,而通告窗口则是接收方进行的流量控制。前者是发送方感受到的网络拥塞的估计,而后者则与接收方在该连接上的可用缓存大小有关。
3) 当拥塞发生时(超时或收到重复确认),ssthresh被设置为当前窗口大小的一半(cwnd和接收方通告窗口大小的最小值,但最少为2个报文段)。此外,如果是超时引起了拥塞,则cwnd被设置为1个报文段(这就是慢启动)。
4) 当新的数据被对方确认时,就增加cwnd,但增加的方法依赖于我们是否在进行慢启动或拥塞避免。如果cwnd小于或等于ssthresh,则在进行慢启动,否则正在进行拥塞避免。慢启动一直持续到我们回到当拥塞发生时所处位置的一半时候才停止(因为我们记录了在步骤2中给我们制造麻烦的窗口大小的一半),然后转为执行拥塞避免。
a) 慢启动算法初始设置cwnd为1个报文段,此后没收到一个确认就加1。这样会使窗口按指数的方式增长:发送1个报文段、然后是2个、接着是4个。
b) 拥塞避免算法要求每次收到一个确认时将cwnd增加1/cwnd。与慢启动的指数增加比起来,这是一种加性增加。我们希望在一个往返时间内最多为cwnd增加1个报文段(不管在这个RTT中收到多少个ACK),然而满启动将根据在这个往返时间中所收到的确认的个数增加cwnd。
2、实现代码
struct tcpcb *
tcp_timers(tp, timer)
register struct tcpcb *tp;
int timer;
{
register int rexmt;
switch (timer) {
......
重传定时器
case TCPT_REXMT:
if (++tp->t_rxtshift > TCP_MAXRXTSHIFT) {
tp->t_rxtshift = TCP_MAXRXTSHIFT;
tcpstat.tcps_timeoutdrop++;
tp = tcp_drop(tp, tp->t_softerror ?
tp->t_softerror : ETIMEDOUT);
break;
}
重传移位计数器(t_rxtshift )在每次重传时递增,如果大于12(TCP_MAXRXTSHIFT),连接将被丢弃。
tcpstat.tcps_rexmttimeo++;
rexmt = TCP_REXMTVAL(tp) * tcp_backoff[tp->t_rxtshift];
TCPT_RANGESET(tp->t_rxtcur, rexmt,
tp->t_rttmin, TCPTV_REXMTMAX);
tp->t_timer[TCPT_REXMT] = tp->t_rxtcur;
利用TCP_REXMTVAL宏实现指数退避,计算新的RTO值。新的RTO值存储在t_rxtcur中,供连接的重传定时器——t_timer[TCPT_REXMT]——使用,tcp_input在启动重传定时器时会用到它。
if (tp->t_rxtshift > TCP_MAXRXTSHIFT / 4) {
in_losing(tp->t_inpcb);
如果报文段以重传4次以上,in_losing将释放缓存中的路由,tcp_output再重传该报文时,将选择一条新的,也许好一些的路由。
tp->t_rttvar += (tp->t_srtt >> TCP_RTT_SHIFT);
tp->t_srtt = 0;
已平滑的RTT估计器(t_srtt)被置为0,强迫tcp_xmit_timer将下一个RTT测量值做为已平滑的RTT估计器,这是因为报文段重传4此后,意味着TCP的以平滑的RTT估计器可能已经失效。
}
tp->snd_nxt = tp->snd_una;
下一个发送序号被置为最早的未确认的序号。
/*
* If timing a segment in this window, stop the timer.
*/
Karn算法
tp->t_rtt = 0;
RTT计数器,t_rtt,被置为0。Karn算法认为:由于报文段即将重传,对该报文段的计时就失去了意义。即使收到了ACK,也无法区分它是对第一次报文还是对第二次报文的确认。
慢启动和避免拥塞
{
u_int win = min(tp->snd_wnd, tp->snd_cwnd) / 2 / tp->t_maxseg;
if (win < 2)
win = 2;
/*将拥塞窗口设为一个报文段的大小,强迫指向满启动*/
tp->snd_cwnd = tp->t_maxseg;
/*将慢启动门限设为拥塞发生时窗口大小的一半*/
tp->snd_ssthresh = win * tp->t_maxseg;
Win被置为现有窗口大小(接收方通告的窗口大小snd_wnd和发送方拥塞窗口大小snd_cwnd ,两者之间的最小值)的一半,以报文为单位而非字节,最小值为2 。它的值等于网络拥塞时现有窗口大小的一半,也就是满启动门限t_ssthresh。拥塞窗口的大小,被置为只容纳1个报文,强迫执行慢启动。上述做法假定造成网络拥塞的原因之一是本地数据发送太快,因此在拥塞发生时,必须降低发送窗口的大小。
tp->t_dupacks = 0;
连续重复ACK计数器,t_dupacks 被置为0。在TCP快速重传和快速恢复算法中将用到它。
}
(void) tcp_output(tp);
tcp_output重新发送包含最早的未确认序号的报文,即由重传定时器超时引发了报文重传。
break;
}
return (tp);
}
三、Karn算法
在一个分组重传时会产生这样一个问题:假定一个分组被发送。当超时发生时, RTO正在进行指数退避,分组以更长的RTO进行重传,然后收到一个确认。那么这个A C K是针对第一个分组的还是针对第二个分组呢?这就是所谓的重传多义性问题。
[Karn and Partridge 1987]规定,当一个超时和重传发生时,在重传数据的确认最后到达
之前,不能更新RT T估计器,因为我们并不知道A C K对应哪次传输(也许第一次传输被延迟而并没有被丢弃,也有可能第一次传输的A C K被延迟)。
并且,由于数据被重传, RTO已经得到了一个指数退避,我们在下一次传输时使用这
个退避后的RTO。对一个没有被重传的报文段而言,除非收到了一个确认,否则不要计算
新的RTO。
实现代码见上一小节中的Karn算法。
四、快速重传与快速恢复算法
1、算法描述
我们知道在收到一个失序的报文段时,TCP立即需要产生一个ACK(一个重复ACK)。这个重复的ACK不应该被迟延。该重复的ACK的目的在于让对方知道收到一个失序的报文段,并告诉对方自己希望收到的序号。
由于我们不知道一个重复的ACK是由一个丢失的报文段引起的,还是由于仅仅出现了几个报文段的重新排序,因此我们等待少量重复的ACK到来。假如这只是一些报文段的重新排序,则在重新排序的报文段被处理并产生一个新的ACK之前,只可能产生1到2个重复的ACK。如果一连串收到3个或3个以上的重复ACK,就非常可能是一个报文段丢失了。于是我们就重传丢失的数据报文段,而无需等待超时定时器溢出。这就是快速重传算法。接下来执行的不是慢启动算法而是拥塞避免算法。这就是快速恢复算法。
算法的执行流程如下:
1) 当收到第3个重复的ACK时,将ssthresh设置为当前窗口的一半。重传丢失的报文段。设置cwnd为ssthresh加上3倍的报文段大小。
2) 每次收到另一个重复的ACK时,cwnd增加1个报文段大小并且发送1个分组(如果新的cwnd允许发送)。
3) 当下一个确认新数据的ACK到达时,设置cwnd为ssthresh(在第1步中设置的值)。这个ACK应该是在进行重传后的一个往返时间内步骤1中重传的确认。另外,这个ACK也应该是对丢失的分组和收到的第1个重复的ACK之间的所有中间报文段的确认。这一步采用的是拥塞避免,因为当分组丢失时我们将当前的收率减半(快速恢复)。
2、实现代码
void
tcp_input(m, iphlen)
register struct mbuf *m;
int iphlen;
{
......
/*
* Ack processing.
*/
switch (tp->t_state) {
case TCPS_ESTABLISHED:
case TCPS_FIN_WAIT_1:
case TCPS_FIN_WAIT_2:
case TCPS_CLOSE_WAIT:
case TCPS_CLOSING:
case TCPS_LAST_ACK:
case TCPS_TIME_WAIT:
if (SEQ_LEQ(ti->ti_ack, tp->snd_una)) {
if (ti->ti_len == 0 && tiwin == tp->snd_wnd) {
tcpstat.tcps_rcvdupack++;
if (tp->t_timer[TCPT_REXMT] == 0 ||
ti->ti_ack != tp->snd_una)
tp->t_dupacks = 0;
连续收到的重复ACK次数己达到门限值3
t_dupacks等于3 (tcprexmtthresh)时,在变量onxt中保存snd_nxt的值,令慢起动门限(ssthresh)等于当前拥塞窗口大小的一半,最小值为两个最大报文段长度。这与重传定时器超时处理中的慢起动门限设定操作类似,但我们将看到,超时处理中把拥塞窗口设定为一个最大报文段长度,快速重传算法并不这样做。
else if (++tp->t_dupacks == tcprexmtthresh) {
tcp_seq onxt = tp->snd_nxt;
u_int win =
min(tp->snd_wnd, tp->snd_cwnd) / 2 /
tp->t_maxseg;
if (win < 2)
win = 2;
tp->snd_ssthresh = win * tp->t_maxseg;
关闭重传定时器
tp->t_timer[TCPT_REXMT] = 0;
tp->t_rtt = 0;
关闭重传定时器。为防止TCP正对某个报文段计时,t_rtt清零。
tp->snd_nxt = ti->ti_ack;
tp->snd_cwnd = tp->t_maxseg;
(void) tcp_output(tp);
从连续收到的重复ACK报文段中可判断出丢失报文段的起始序号(重复ACK的确认字段),将其赋给sndnxt,并将拥塞窗口设定为一个最大报文段长度,从而tcp_output将只发送丢失报文段。
设定拥塞窗口
tp->snd_cwnd = tp->snd_ssthresh +
tp->t_maxseg * tp->t_dupacks;
拥塞窗口等于慢启动门限加上对端高速缓存的报文段数。“高速缓存”指对端已收到的乱序报文段数,且为这些报文段发送了重复的ACK。
设定sndnxt
if (SEQ_GT(onxt, tp->snd_nxt))
tp->snd_nxt = onxt;
比较下一发送序号(sndnxt)的先前值(onxt)和当前值,将两者中最大的一个重新赋还给snd _nxt,因为重传报文段时,tcp_output会改变snd_nxt。一般情况下,sndnxt将等于原来保存的值,意味着只有丢失报文段被重传,下一次调用tcpoutput时,将继续发送序列中的下一报文段。
goto drop;
}
连续收到的重复ACK数超过门限3
else if (tp->t_dupacks > tcprexmtthresh) {
tp->snd_cwnd += tp->t_maxseg;
(void) tcp_output(tp);
goto drop;
因为t_dupacks 等于3时,已重传了丢失的报文段,再次收到重复ACK说明又有另一个报文段离开了网络。拥塞窗口大小加1,调用tcpoutput发送序列中的下一报文段,并丢弃重复的A C K
}
} else
tp->t_dupacks = 0;
break;
}
调整拥塞窗口
if (tp->t_dupacks > tcprexmtthresh &&
tp->snd_cwnd > tp->snd_ssthresh)
tp->snd_cwnd = tp->snd_ssthresh;
tp->t_dupacks = 0;
如果连续收到的重复ACK数超过了门限值3,说明这是在收到了4个或4个以上的
重复ACK后,收到的第一个非重复的ACK。快速重传算法结束。因为从收到的第4个重复
ACK开始,每收到一个重复ACK就会导致拥塞窗口加1,如果它已超过了慢起动门限,令其
等于慢起动门限。连续收到的重复ACK计数器清零
...
}
五、糊涂窗口综合症状
1、算法描述
基于窗口的流量控制方案,如T C P所使用的,会导致一种被称为“糊涂窗口综合症S W S(Silly Window Syndrome)”的状况。如果发生这种情况,则少量的数据将通过连接进行交换,而不是满长度的报文段[Clark 1982]。
该现象可发生在两端中的任何一端:接收方可以通告一个小的窗口(而不是一直等到有
大的窗口时才通告),而发送方也可以发送少量的数据(而不是等待其他的数据以便发送一个大的报文段)。
可以在任何一端采取措施避免出现糊涂窗口综合症的现象。
1) 接收方不通告小窗口。通常的算法是接收方不通告一个比当前窗口大的窗口(可以为0),除非窗口可以增加一个报文段的大小(也就是将要接收的MSS)或者可以增加接收方缓存空间的一半,不论实际有多少。
2) 发送方避免糊涂窗口综合症的措施是只有以下条件之一满足时才发送数据:
a) 可以发送一个满长度的报文段;
b) 可以发送至少是接受方通告窗口大小一半的报文段;
c) 可以发送任何数据并且不希望接收ACK(也就是说,我们没有未被确认的数据)或者该连接上不能使用Nagle算法。
条件( b )主要对付那些总是通告小窗口(也许比1个报文段还小)的主机;
条件( c )使我们在有尚未被确认的数据(正在等待被确认)以及在不能使用N a g l e算法的情况下,避免发送小的报文段。如果应用进程在进行小数据的写操作(例如比该报文段还小),条件( c )可以避免出现糊涂窗口综合症。
这三个条件也可以让我们回答这样一个问题:在有尚未被确认数据的情况下,如果N a g l e算法阻止我们发送小的报文段,那么多小才算是小呢?从条件( a )中可以看出所谓“小”就是指字节数小于报文段的大小。
条件( b )仅用来对付较老的、原始的主机。
步骤2中的条件( b )要求发送方始终监视另一方通告的最大窗口大小,这是一种发送方猜测对方接收缓存大小的企图。虽然在连接建立时接收缓存的大小可能会减小,但在实际中这种情况很少见。
2、源码分析
1)发送方源码
int
tcp_output(tp)
register struct tcpcb *tp;
{
......
/*win本地接收窗口中可用空间的大小,即TCP向对端通告
的接收窗口的大小*/
win = sbspace(&so->so_rcv);
/*
* Sender silly window avoidance. If connection is idle
* and can send all data, a maximum segment,
* at least a maximum default-size segment do it,
* or are forced, do it; otherwise don't bother.
* If peer's buffer is tiny, then send
* when window is at least half open.
* If retransmitting (possibly after persist timer forced us
* to send into a small window), then must resend.
*/
/*len为将要发送的报文段的长度*/
if (len) {
可以发送一个满长度的报文段
if (len == tp->t_maxseg)
goto send;
/*如果待发送报文段是最大长度报文段,则发送它。*/
可以发送任何数据并且不希望接收ACK或者该连接上不能使用Nagle算法
if ((idle || tp->t_flags & TF_NODELAY) &&
len + off >= so->so_snd.sb_cc)
goto send;
/*如果无需等待对端的ACK(idle为真),或者Nagle算法取消,并且TCP正在清空发送缓存,则发送数据*/
if (tp->t_force)
goto send;
/*如果由于持续定时器超时,或者有紧急数据,强迫TCP执行发送操作*/
可以发送至少是接收方通告窗口大小一半的报文段
if (len >= tp->max_sndwnd / 2)
goto send;
/* tp->max_sndwnd:接收方通过的窗口的所有窗口中最大的窗口值,实际上,TCP试图猜测对端接收缓存的大小,并假定对端永远不会减少其接收缓存。如果接收方窗口已至少打开了一半,则发送数据。这个限制是为了处理对端一直发送小窗口通告,甚至小于报文段长度的情况*/
if (SEQ_LT(tp->snd_nxt, tp->snd_max))
goto send;
/*如果重传报文段超时,则必须发送一个报文段*/
}
2)接收方源码
/*
* Compare available window to amount of window
* known to peer (as advertised window less
* next expected input). If the difference is at least two
* max size segments, or at least 50% of the maximum possible
* window, then want to send a window update to peer.
*/
if (win > 0) {
/*
* "adv" is the amount we can increase the window,
* taking into account that we are limited by
* TCP_MAXWIN << tp->rcv_scale.
*/
long adv = min(win, (long)TCP_MAXWIN << tp->rcv_scale) -
(tp->rcv_adv - tp->rcv_nxt);
窗口可以增加两个报文段大小
if (adv >= (long) (2 * tp->t_maxseg))
goto send;
/*如果接收窗口已打开(可以右移的字节数)两个和两个以上的报文段,则发送窗口更新报文*/
窗口可以增加接收缓存的一半
if (2 * adv >= (long) so->so_rcv.sb_hiwat)
goto send;
/*如果窗口以打开插口接收缓存的一半,则发送窗口更新报文*/
}
......
}
计算向对端通告窗口的大小
/*
* Calculate receive window. Don't shrink window,
* but avoid silly window syndrome.
*/
if (win < (long)(so->so_rcv.sb_hiwat / 4) && win < (long)tp->t_maxseg)
win = 0;
/*如果win小于接收缓存的1/4,并且小于一个最大报文段长度,则通告窗口的大小设为0,从而在后续测试中防止窗口缩小。也就是说,如果可用空间已达到接收缓存大小的1/4,或者等于最大报文长度,将向对端发送窗口更新通告。*/
if (win > (long)TCP_MAXWIN << tp->rcv_scale)
win = (long)TCP_MAXWIN << tp->rcv_scale;
/*如果win大于连接的规定值,应将其减小为最大值*/
if (win < (long)(tp->rcv_adv - tp->rcv_nxt))
win = (long)(tp->rcv_adv - tp->rcv_nxt);
/*rcv_adv减去rcv_nxt等于最近一次向发送方通告的窗口大小中剩余的空间。如果win小于它,应将其设定为该值,因为不容许缩小窗口。*/
ti->ti_win = htons((u_short) (win>>tp->rcv_scale));
rcv_adv的更新
if (win > 0 && SEQ_GT(tp->rcv_nxt+win, tp->rcv_adv))
tp->rcv_adv = tp->rcv_nxt + win;
在win大于0的情况下,才会更新win;也就是说,如果win为0,将不会更新rcv_adv。如果报文段中通告的最高序号(rcv_nxt加上win)大于rcv_adv,则保存新的值。
TCP可靠传输及流量控制系列五:TCP流量控制基本算法相关推荐
- TCP 可靠传输机制详解
目录 TCP协议的特点 TCP 报文段 TCP "三次握手" TCP "四次挥手" 客户端和服务器端所经历的状态 TCP 可靠传输 TCP流量控制 TCP拥塞控 ...
- 计算机网络之传输层:5、TCP可靠传输
传输层:5.TCP可靠传输 TCP可靠传输: 伪首部校验: 实现可靠传输的过程: 实现可靠传输的工作原理: 实现流量控制的工作原理: TCP可靠传输: 伪首部校验: 与UDP校验一样,增加伪首部进行校 ...
- 5.3.3 TCP可靠传输
5.3.3 TCP可靠传输
- 【计算机网络】传输层 : TCP 可靠传输 ( 可靠传输机制 | 快速重传机制 )
文章目录 一.TCP 可靠传输 二.TCP 可靠传输机制 三.TCP 快速重传 机制 一.TCP 可靠传输 可靠性 : 保证 接收方进程 从 TCP 缓冲区 中读取的数据 与 发送方进程 发送的数据 ...
- 滑动窗口——TCP可靠传输的实现[转]
滑动窗口--TCP可靠传输的实现[转] 转自: http://hi.baidu.com/bellgrade/blog/item/935a432393b949ae4723e828.html (1).窗 ...
- 5.3.1计算机网络传输层之TCP可靠传输
文章目录 0.前言 1.TCP可靠传输简介 2.序号 3.确认 4.重传 0.前言 再看此篇文章之前,得熟悉一下TCP首部报文等知识 计算机网络传输层之TCP协议(tcp协议特点.tcp报文段首部格式 ...
- 计算机网络(9)-----TCP可靠传输的实现
TCP可靠传输的实现 以字节为单位的滑动窗口 滑动窗口的滑动是以字节为单位的,发送方A和接收方B在TCP三次握手的前两次握手时协商好了发送窗口和接受窗口的大小,发送方A根据B发送来的确认连接报文中标明 ...
- 网络协议从入门到底层原理(5)传输层(UDP、TCP - 可靠传输、流量控制、拥塞控制、建立连接、释放连接)
传输层( Transport) 传输层( Transport) UDP 协议(数据格式.检验和) 端口(Port) TCP TCP - 数据偏移.保留 TCP - 检验和( CheckSum) TCP ...
- tcp可靠传输的机制有哪些(面试必看
一.综述 1.确认和重传:接收方收到报文就会确认,发送方发送一段时间后没有收到确认就重传. 2.数据校验 3.数据合理分片和排序: UDP:IP数据报大于1500字节,大于MTU.这个时候发送方IP层 ...
- TCP 可靠传输机制
TCP:传输控制协议 TCP: 是一种面向连接的可靠的传输协议 什么是可靠?->保证数据传输给对方 怎么保证可靠性?-> 确认机制 重传输机制 什么是面向连接?->在传输数据之前,双 ...
最新文章
- 搞懂分布式技术8:负载均衡原理剖析
- VTK:几何对象之EarthSource
- python生成器和迭代器区别_生成器、迭代器的区别?
- mysql 8.0 一条insert语句的具体执行流程分析(一)
- 当前服务器文件夹不存在,供应商文件夹不存在,无法创建
- 中累计直方图_新特性解读 | MySQL 8.0 直方图
- 【Java】Java对象引用四个级别(强、软、弱、虚)
- C++11 std::bind
- models.TABLE.objects.filter()与models.TABLE.objects.get()的区别
- android支持u盘格式文件,安卓系统OTG支持U盘格式
- 台式计算机无故重启,台式电脑突然自动重启是怎么回事
- elasticsearch报错:bootstrap checks failed. You must address the points described in the following [1]
- 警告: Failed to register object [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/Qia
- 突破领英限制如何查找非好友电话,邮箱技巧
- 【信号处理】因果系统的理解
- tomcat下载、安装、配置(新手教程墨迹版)
- 星星是怎么来的?—— CG短片《繁星》幕后分享
- C语言PAT刷题 - 1027 打印沙漏
- 几招搞定如何发送招聘兼职通知面试短信
- 网上招投标系统 php,招投标管理系统