TCP协议设计原理


TCP协议设计原理

  最近去了解TCP协议,发现这是一个特别值得深思的协议。在本篇博客中,不会长篇大论的给你们介绍TCP协议特色、包头格式以及TCP的链接和断开等基本原理,而是会带你们深刻理解为何要这么设计,若是不这么设计,会产生什么后果,但愿能帮助你们对TCP协议的理解。TCP弥补了IP尽力而为服务的不足,实现了面向链接、高可靠性、报文按序到达、端到端流量控制算法

  • 面向链接

  一提到TCP是面向链接的协议,必然是介绍其的3次握手和4次挥手,为了说明为何须要三次握手和四次挥手,咱们仍是拿两个图来讲明链接创建和断开的过程。服务器

 

  为何要三次握手呢?若两次握手怎样。假设客户端发起链接请求(SYN=1,seq=client_isn),服务器端收到请求后返回消息(SYN=1,seq=server_isn,ack=client+1)链接创建。网络

  如今说明为何两次握手不能够。若客户端发送链接请求request1(SYN=1,seq=client_isn),这时这个请求因为网络阻塞没有及时到达服务器端,而客户端一段时间后又发送了一个链接请求request2 (SYN=1,seq=client_isn),该request2创建了链接完成了本次通讯,而后断开链接。此时客户端发送的第一个链接请求request1到达了服务器端,此时服务器端发现是一个链接请求,服务端并不知道这是因为网络阻塞致使已经无用的链接请求,服务器收到request1则给客户端发送消息(SYN=1,seq=server_isn,ack=client_isn+1)。若是是两次握手那么客户端在收到这条消息后则客户端和服务器端创建链接。但客户端并非真正想创建链接,因此不能经过两次握手就创建链接。学习

  那为何须要四次挥手呢?若是三次挥手又会怎样。咱们假设客户端向服务器发送了断开请求,服务器在收到断开请求后也向客户端发送断开请求(FIN=1,ACK=1,seq=w,ack=u+1),客户端收到此消息后向服务器发送断开链接(ACK=1,seq=u+1,ack=w+1)。可想而知这种方法是不可行的。由于当客户端没有数据须要发送给服务器时,客户端主动发起了断开请求,可是并不表明服务器端没有数据发给客户端。因此为了保证服务器端正常传输完数据,服务器端在收到客户端发送的断开请求后先发送一个ACK(ACK=1,seq=v,ack=u+1)给客户端,当服务器端数据传输完后发送断开请求(FIN=1,ACK=1,seq=w,ack=u+1)。spa

  不知道你们有没有注意到客户端在发送了最后一个断开请求的ACK后,又等待了2MSL的时间才关闭链接。为何不直接关闭链接呢?若是客户端直接关闭链接,而此时客户端最后发送的ACK又在网络中丢失,从而可能致使服务器端的链接没法正常关闭。那为何又要设置为2MSL呢?1MSL表示一个IP数据报在网络中的最多存活时间。假设客户端最后发送的ACK通过将近1MSL快要到达服务器端的时候丢失了,那么服务器端在规定的时间内未收到最后客户端发送的ACK,则服务器端从新发送最后的FIN给客户端,请求客户端重发ACK,该FIN通过1MSL到达客户端。因此如上最坏状况,若是客户端在2MSL内没有收到FIN请求,则代表服务器端已经断开链接。设计

  • 传输高可靠性

  不用多说,你们都知道TCP的传输可靠性是依据确认号实现的。简单说就是客户端每发送一个分段给服务器端,服务端收到后会给客户端发送一个确认号,表示服务器端收到该分段。若是客户端在RTT时间周期内未收到服务器端的确认号,则引起超时重传。所以TCP协议中须要计时器。那么问题就来了,TCP有那么多分段,是要给每个分段都生成一个计时器吗?code

  给每一个分段都生成一个计时器固然是最简单也最好理解的,每一个计时器在RTT时间后到期,若是没有收到确认号则重传该分段。然而给每一个分段都生成计时器将带来巨大的内存开销和调度开销。所以在实际中采起给每一个TCP链接生成一个计时器,那么问题又来了,一个TCP链接有那么多分段,如何利用一个计时器管理这么多分段呢?设计原则以下(你们能够思考一下为何这么设计):server

  1. 发送TCP分段时,若是没有开启重传定时器,则开启;
  2. 发送TCP分段时,若是有重传定时器开启,则再也不开启;
  3. 收到一个非冗余的ACK时,若是有数据在传输,从新启动重传定时器;
  4. 收到一个非冗余的ACK时,若是没有数据在传输,则关闭重传定时器;
  5. 若是连续收到3个冗余ACK时,则不用等到重传定时器超时,直接重传。
  • 报文按序到达

  确认号是TCP两端通讯的数据传输的“标志”,TCP的发送端在收到一个确认号后,就认为接收端已经收到了该确认号以前的全部数据。早期的TCP标准中,只要TCP有一个分段丢失,该分段后的其余分段即便正确到达接收端,发送端仍是会重传丢失分段后的全部分段,从而致使了大量没必要要的超时重传。如今的TCP实现了一种选择确认的方式,接收端会显示的告诉发送端重传哪些分段,不须要重传哪些分段,避免了重传风暴。blog

  不知道你们在学习TCP协议时,有没有考虑TCP序列号回绕的问题。从TCP报文头部知道序列号占32位,能传输2的32次方个字节。若是一个1Gbps的网络,TCP端1s会发送125MB的数据,从而在32s内可发送2的32次方个字节,致使序列号回绕,而32s是小于MSL值的。一旦序列号回绕会致使接收端对TCP报文的排序发生错乱。固然能够经过加时间戳的方式来辅助序列号的识别,在接收端发现序列号回绕时,比较时间戳字段的值,若是回绕的序列号时间戳较大,则说明确实发生了回绕,从而将该数据放在最大的序列号以后。TCP还有其余方法判断序列号是否发生回绕,从而有效的肯定数据报的排列顺序。排序

  • 端到端流量控制

  端到端流量控制使用滑动窗口来实现,一提到滑动窗口你们张口就来的是慢开始、拥塞避免、快重传、快恢复。那么问题来了:①快重传和快恢复确实提升了TCP的传输效率,可是若是发送端每次发送的TCP报文中仅有少许的数据,而包含大量的报头字段,从而也会影响效率,那么如何增大发送端发送数据的大小呢。②接收端在收到数据后返回给发送端一个ACK,若是接收端针对每一个分段都返回ACK的话,网络中的ACK也会消耗大量的带宽,那么如何减小网络中ACK的发送呢。

  你们可能看到这样的长篇大论,已经没有了任何兴趣,那就放一张卡车拉煤图吧。我想经过卡车拉煤来讲明如何解决这两个问题。其中括号中的是TCP中问题用拉煤的例子解释。

  咱们先说第一个问题,就是TCP每次携带数据量少(卡车每次都拉一点煤,都不够油钱的)的问题。TCP中为何会存在这个问题呢?接收端经过ack告诉发送端接收端窗口大小,决定发送端还能够发送多少数据(北京发电厂告诉山西煤场我这最多还能够接受5kg煤,你下次就送5kg煤就能够了,而后山西煤场就真的开着卡车送来了5kg煤)。这种状况显然须要从接收端着手解决,若是接收窗口为0,则告诉发送端不要在发送数据了,只有当接收端可接受的数据达到接收窗口的一半时,再告诉发送窗口发送数据(也就是说北京发电厂已经腾出了一半的空地可放煤了,才告知山西煤场送煤)。那还存在问题,虽然接受窗口已经有一半空闲,可是发送窗口发送的TCP携带的数据量仍是较少(虽然发电厂已经有一半的地能够放煤了,可是煤场每次只送5kg煤)。这就是发送端的问题了,从而利用Nagle算法解决发送端持续发送小块数据分段的问题。以下咱们就来看看这个Nagle算法:

IF 数据的大小和窗口的大小都超过了MSS
Then 发送数据分段
ELSE
  IF 还有发出的不足MSS大小的TCP分段没有收到确认
    Then 积累数据到发送队列的末尾的TCP分段
  ELSE
    发送数据分段
  EndIF
EndIF

  第二个问题就是网络中ACK消耗大量带宽的问题(也就是说卡车把煤拉到北京,直接带着北京的口信,空着车就回山西了)。RFC建议了一种延迟的ACK,也就是说接收端在收到数据并不当即回复ACK,而是等一段时间,看看接收端是否也有数据要发送给发送端,同时经过要发送的数据一同传输给发送端。等一段时间,可能后续的TCP分段到达,这样就能够取最大者一块儿返回,从而也能减小网络中ACK的数量。固然RFC的建议延迟的ACK最多等待两个分段的积累确认。

相关文章
相关标签/搜索