什么样的情况出现说明网络出现了拥塞?出现发送数据报文超时和收到3个重复的ACK。这两种情况都说明数据报文段有丢失的情况,我们假定由于报文损坏引起的丢失概率是很低的,因此出现报文丢失,就说明源主机和目的主机之间出现了拥塞。
TCP是一个可靠的协议,在发送数据后会启动一个超时重传定时器,如果超过RTO时间还未收到ACK,就会重传数据。重传会加重网络的负载,会导致更多的丢包和延迟,因此是无节制的重传不是一个好方法,它之后使得网络更加拥塞。
拥塞控制主要有四种方法:
1) 慢启动
2) 拥塞避免
3) 快速重传
4) 快速恢复
慢启动
最初的TCP在连接建立成功后会向网络中发送大量的数据包,这样很容易导致网络中路由器缓存空间耗尽,从而发生拥塞的情况。慢启动就是新建立的连接,速度是一点一点慢慢的加起来,该算法是通过观察新分组进入网络的速率应该与另一端返回ACK确认的速率相同。
慢启动为发生方增加了一个窗口:拥塞窗口cwnd(congestion window)。当与另外一个网络的主机建立连接时,cwnd初始化1个报文段(MSS)。
拥塞窗口是发送方使用的流量控制,发送窗口是接收方使用的流量控制。发送方取拥塞窗口与通过窗口中较小值作为发送上限。
慢启动算法如下:
1)初始化cwnd=1,为一个报文段,表示可以传输一个MSS大小的数据。慢启动门限ssthresh值初始化为65536。
2)每当收到一个ACK,cwnd++
3)每一个RTT,cwnd=2*cwnd,cwnd的值呈指数级的增长。
下面结合lwip代码看下慢启动是如何实现的。在客户端调用tcp_connect函数发送一个SYN报文执行主动建链时:
pcb->cwnd = 1; pcb->ssthresh = TCP_WND; |
拥塞窗口cwnd初始化为1个报文段,即MSS大小。慢启动门限ssthresh初始化为TCP_WND,此值可以在opt.h中更改,一般为4*TCP_MSS大小。
在服务器端处于listen状态,在tcp_listen_input函数中处理客户端发过来的SYN报文,并设置cwnd和ssthresh值:
npcb->snd_wnd = SND_WND_SCALE(npcb, tcphdr->wnd ); npcb->ssthresh = LWIP_TCP_INITIAL_SSTHRESH(npcb); |
snd_wnd表示发送方的窗口大小,慢启动门限ssthresh的值设置为snd_wnd的大小。
在客户端收到服务器发过来的SYN+ACK报文后,在tcp_process函数中做如下设置:
pcb->ssthresh = LWIP_TCP_INITIAL_SSTHRESH(pcb); pcb->cwnd = LWIP_TCP_CALC_INITIAL_CWND(pcb->mss ); |
慢启动门限ssthresh值设置为对端的发送窗口snd_wnd,拥塞窗口cwnd值设置为4*MSS大小。
每当收到一个ACK,cwnd值就会加1。Lwip的tcp_receive函数用来处理接收到的ACK报文:
/* Update the congestion control variables (cwnd and ssthresh). */ if (pcb->cwnd < pcb->ssthresh ) { /*执行慢启动*/ if ((tcpwnd_size_t)(pcb->cwnd + pcb->mss ) > pcb->cwnd ) { pcb->cwnd += pcb->mss ; /*拥塞窗口增加一个报文段*/ } LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: slow start cwnd %"TCPWNDSIZE_F"\n", pcb->cwnd )); |
当cwnd < ssthresh时,执行慢启动,cwnd的值增加mss大小,即一个报文段。
拥塞避免
当拥塞窗口cwnd的值增加到大于等于慢启动门限ssthresh的值,就会进入拥塞避免。拥塞避免的主要思想是cwnd的值不再指数级增长,而是加法增加。
拥塞避免算法如下:
1)当收到一个ACK后,cwnd += 1/cwnd
这样保证了在一个RTT内,cwnd的增加值不会超过1,从而避免了cwnd增加过快,导致网络拥塞。
Lwip中执行拥塞避免相关的代码如下:
tcpwnd_size_t new_cwnd = (pcb->cwnd + pcb->mss * pcb->mss / pcb->cwnd ); if (new_cwnd > pcb->cwnd ) { pcb->cwnd = new_cwnd; } LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: congestion avoidance cwnd %"TCPWNDSIZE_F"\n", pcb->cwnd )); |
上面的代码就是处理拥塞避免部分的代码,代码实现部分cwnd值在每次收到一个ACK后,增加了mss*mss/cwnd大小。
慢启动和拥塞避免是在没有发生拥塞时的拥塞控制机制。
快速重传
TCP协议通信时,发送的数据和ACK确认都有可能丢失。TCP协议在发送时会设置一个超时定时器来解决这种丢失的问题。如果此定时器超时,就会重传数据。
接收方在收到一个失序的报文时,会给发送方回一个ACK,告诉发送方期待收到的下一个字节。这个ACK一定是一个重复的ACK,接收方在收到这个重复的ACK后,不会立马认为这是由于报文丢失引起的,因为也有可能是由于几个报文段的乱序引起的。
如果收到3个重复的ACK,就认为是一个报文段丢失了。这样就会重传丢失的报文段,而不需要等待超时定时器的超时,这就是快速重传算法。