码迷,mamicode.com
首页 > 其他好文 > 详细

《TCP/IP详解:卷一》-TCP部分讲解

时间:2015-07-02 01:04:52      阅读:292      评论:0      收藏:0      [点我收藏+]

标签:

TCP/IP协议

作者:Danbo 2015-7-2
本文为参考TCP/IP详解卷一,某些知识点加上了作者自己的理解,如有错误,欢迎指正,可以微博联系我!

TCP包格式和IP包格式如下:

技术分享

技术分享

TCP的正常建立与关闭

技术分享

建立连接

TCP协议提供可靠的面向连接服务,采用三次握手建立连接。
第一次握手:建立连接时,客户端发送SYN包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到SYN包,向客户端返回ACK(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RCVD状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据,也就是ESTABLISHED状态。 

 

终止连接

采用四次挥手断开双向连接。
(1) TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送。
(2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
(3) 服务器关闭客户端的连接,发送一个FIN给客户端。
(4) 客户端发回ACK报文确认,并将确认序号设置为收到序号加1。

TCP状态变迁图

技术分享

 

客户端的状态可以用一下流程图来表示:

CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED

服务器的状态可以流程图:

CLOSED->LISTEN->SYN收到 ->ESTABLISHED->CLOSE_WAIT->LAST->ACK->CLOSED

 

2MSL等待状态

TIME_WAIT状态也称为2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。处理原则:当TCP执行一个主动关闭,并发回最后一个ACK,该链接必须在TIME_WAIT状态停留的时间为2MSL。这样可让TCP在此发送最后一个ACK以防这个ACK丢失(在另一端发送FIN前提)

但是,在连接处于2MSL等待时,任何迟到的报文段将被丢弃。因为处于2MSL等待的、由该插口对(socket pair)定义的连接在这段时间内不能被再用,对于客户程序还好 一些,但是对于服务程序,例如httpd,它总是要使用同一个端口80来进行服务,而在 2MSL时间内,启动httpd就会出现错误(插口被使用)。为了避免这个错误,服务器给出了一个平静时间(quit time)的概念,这是说在2MSL时间内,虽然可以重新启动服务器,但是这个服务器还是要平静的等待2MSL时间的过去才能进行下一次连接。

 

半打开状态(Half-Open)

如果以防已经关闭或异常终止连接而另一方却不知道,我们将这样的TCP连接称为半打开的。这种状态可以通过Keepalive选项来进行发现两一段已经消失。

当处于版打开状态的一方重启并重新连接后,它将丢失复位前的所有信息,因此它并不知道数据报文段中提到的连接。此时就会返回RST包应答,以重新建立新的连接。

 

半关闭状态(Half-Close)

单方向链路关闭。即TCP连接一端在结束它的发送后还能接收来自另一端数据的能力。程序调用的是shutdown,而不是close,不过大多数程序都是调用close终止两个方向的连接。

 

最大报文段长度MSS

最大报文段长度表示TCP传往另一端的最大块数据的长度。当建立一个连接时,每一方都受到对方通告的MSS值(MSS选项只能出现在SYN报文中)。如果一方收不到另一方的MSS值,那么就设为默认的536字节。

纳格算法

网络中某个应用程序不断地送出小单位的资料,且某些常是1字节大小。因为TCP封包具有40字节的总头部(加上20字节的IP头部),这导致41字节大小的包只有一字节的数据,这造成了极大的资源浪费,更糟糕的是在慢速网络下,这类包造成拥塞碰撞(Congestion Collapse)
 
Nagle算法过程:
1.发送端TCP将它从发送应用程序收到的第一个数据发送出去,哪怕只有一个字节;
2.在发送出第一个报文段后,发送端的TCP数据包就会在输出缓存中积累并等待,当从接收端收到对上一个数据包的ACK或者缓存中积累到一个最大报文段后,发送端TCP就可以发送这个报文段了。
Nagle算法的优点就是简单,并且它考虑到应用程序产生数据的速率,以及网络运输数据的速率。若应用程序比网络更快,则报文段就更大(最大报文段)。若应用程序比网络慢,则报文段就较小(小于最大报文段)。
 

经受时延的确认

通常TCP在接收到数据时并不立即发送ACK。通常TCP在接收到数据时并不立即发送ACK;相反,它推迟发送,以便将ACK与需要沿该方向发送的数据一起发送(有时称这种现象为数据捎带ACK)。
 

糊涂窗口综合征

当发送端应用程序产生数据很慢、或者接收端应用程序处理接收缓存区的数据很慢的时候,就会在链路中传送很小的报文段,极端情况下有小负载只有1字节而报文段却又41字节。这种现象叫做糊涂窗口综合征(Silly Window Syndrome)。
 
可以在发送方或接收方任意一方采取措施来避免这种现象
在接收端避免措施
接收方不通告小窗口,通常算法是接收方不通告一个比当前窗口大的窗口除非窗口可以增加一个报文段大小(将要接收MSS的大小)、或者可以在增加接收方缓存空间的一半。
 
在发送端避免措施
发送方在满足一下条件之一后才会发送数据:1.可以发送一个满长度的报文段;2.可以发送至少是接收方通告窗口大小一半的报文段;3.能够发送手头的所有数据并且不希望接收ACK或者改连接禁用了纳格算法。
 

慢启动

如果发送方一开始便向网络发送多个报文段,知道达到接收方通告的窗口大小为止。当发送方和接收方位于同一局域网还好。但是如果发送方和接收方之间存在多个路由器和速率较慢的链路时,可能出现问题。中间的路由器必须缓存分组,并有可能耗尽存储器的空间。现在TCP支持一种被称为“慢启动(slow start)”的算法。该算法核心是让新分组进入网络的速率与另一端返回确认的速率相同而进行工作。
 
慢启动为发送方的TCP增加了另一窗口:拥塞窗口(congestion window,cwnd)当与另一个网络的主机建立TCP连接时,拥塞窗口被初始化为1个报文段。每收到一个ACK,拥塞窗口就增加一个报文段。以此成指数增长方式。发送方去拥塞窗口和通告窗口的最小值作为发送上限。拥塞窗口是发送方使用的流量控制,而通告窗口则是接收方使用的流量控制。
 

TCP超时与重传

TCP超时重传采用指数退避的算法(exponential backoff)对连续重传之间不同的时间差,他们取整后分别为1\3\6\12\24\48\64(最大值为64)
 

拥塞避免算法

拥塞算法是一种处理丢失分组的方法。网络发生分组丢失的指示:发生超时和收到重复的ACK(3个或3个以上)
拥塞避免算法和慢启动算法是两个目的不同、独立的算法。但是方发生拥塞时,我们下午给你降低分组进入网络的传输速率,于是可以调用慢启动来作到这一点。在实际中这两个算法通常在一起使用。
 
拥塞避免算法和慢启动算法需要对每个连接维持两个变量:一个拥塞窗口(cwnd)和一个慢启动门限(ssthresh)。这样得到的算法的工作过程如下:
1)对一个给定的连接,初始化cwnd为1个报文段,ssthresh为65535个字节;
2)TCP输出数据大小不能超过cwnd和接收方通告窗口的大小。拥塞避免是发送方使用的流量控制,而通告窗口是接收方进行的流量控制。前置是发送方感受到网络拥塞的估计,后者则与接收方在该连接上的可用缓存大小有关;
3)当拥塞发生时(超时或收到重复确认),ssthresh被设置为当前窗口的一半,但至少为2个报文段大小。如果是超时引起的拥塞,则cwnd被设置为1个报文段(这就是慢启动);
4)当新的数据被对方确实时,就增加cwnd,但增加的方法依赖于我们是否在进行慢启动或拥塞避免。如果cwnd≤ssthresh,则正在进行慢启动,反之进行拥塞避免。慢启动一直持续到我们我们回到当拥塞发生时所处位置一半的时候才停止(即新的ssthresh)然后转为执行拥塞避免。
慢启动算法初始cwnd为1个报文段,每收到一个ack后cwnd就增加1(注意TCP是累计确认),那样,窗口会以指数方式增长。
拥塞避免算法要求每次收到确认时将cwnd增加1/cwnd,这是个线性增长。我们希望在一个往返时间内最多为cwnd增加1个报文段,不管在这个RTT中收到了多少个ACK,然后慢启动则是根据这个往返时间中所收到的确认的个数增加cwnd。
 
下图是慢启动和拥塞避免的可视化描述
技术分享
 
解释:上图中,假定当cwnd为32个报文段时就会发生拥塞。于是设置ssthresh为16个报文段,而cwnd为1个报文段。在时刻0发送一个报文段,并假设在时刻1接收到它的ACK,此时cwnd增加为2.接着发送了2个报文段,并假设在时刻2接收到他们的ACK,于是cwnd增加为4(对每个ACK增加1次)。这种指数增加算法一直进行到在时刻3和时刻4之间收到8个ACK后cwnd等于ssthresh时才停止,从该时刻起,cwnd以线性方式增加,在每个往返时间内最多增加1个报文段。
正如我们在这个图中看到的那样,术语“慢启动”并不完全正确。它只是采用了比引起拥塞更慢的分组传输速率,但在慢启动期间进入网络分组速率依然是增加的。只有在达到ssthresh拥塞避免算法起作用时,这种增加的速率才会慢下来。
 

快速重传与快速恢复算法

算法通常按如下过程进行实现:
1)当收到第3个重复的ACK时,将ssthresh设置为当前cwnd的一半。重传丢失的报文段。然后设置cwnd为当前ssthresh加上3倍的报文段大小。
代码实现为:[java] view plaincopy step1:if ( dupacks >= 3 ) { ssthresh = max( 2 , cwnd / 2 ) ;cwnd = ssthresh + 3 * SMSS ;}
2)每次收到另一个重复的ACK时,cwnd++,并发送1个分组。注意!!是先按照上次cwnd发送数据包,然后再使cwnd增加一个报文段大小。
3)当下一个确认新数据的ACK达到时,设置cwnd为ssthresh。这个ACK应该是在进行重传后的一个往返时间内的所有中间报文段的确认。这一步采用的是拥塞避免,因为当分组丢失时我们将当前的速率减半。
下图是拥塞避免的一个例子:
技术分享
我们注意当cwnd为512时进行慢启动,因为只有当cwnd大于ssthresh才进行拥塞避免,当cwnd为768时此时进行的还是慢启动,注意因为cwnd的增加是进行发送数据包之后的时,代码实现是:cwnd++即:进行完发包后才进行自加的!
 

TCP坚持定时器

当一个通告窗口变化的ACK丢失后,则双方就有可能因为等待对方而使连接终止:接收方等待接收数据(因为它已经向发送方通告了一个非0的窗口),而发送方在等待允许它继续发送数据的窗口更新。为防止这种死锁情况的发生,发送方使用一个坚持定时器(persist timer)来周期性地向接收方查询,以便发现窗口是否增大。这些从发送发发出的报文段称为窗口探查(window probe)。
同样使用指数退避的方式发送坚持定时器,TCP从不放弃发送窗口探查。这些探查每隔60s发送一次,这个过程将持续到或者窗口被打开,或者应用程序使用的连接被终止。
 

TCP保活定时器

keepalive
 

版权声明:本文为博主原创文章,未经博主允许不得转载。

《TCP/IP详解:卷一》-TCP部分讲解

标签:

原文地址:http://www.cnblogs.com/danbo/p/4614957.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!