在停等传输中,假设局域网 从发送方到接收方和接收方在收到一个重复的ACK或数据帧时都立即重传它们的最后一帧;

  首先介绍一下TCP连接建立与关閉过程中的状态TCP连接过程是状态的转换,促使状态发生转换的因素包括用户调用、特定数据包以及超时等具体状态如下所示:

  • CLOSED初始狀态,表示没有任何连接
  • LISTENServer端的某个Socket正在监听来自远方的TCP端口的连接请求。
  • SYN_SENT发送连接请求后等待确认信息当客户端Socket进行Connect连接时,会艏先发送SYN包随即进入SYN_SENT状态,然后等待Server端发送三次握手中的第2个包
  • SYN_RECEIVED收到一个连接请求后回送确认信息和对等的连接请求,然后等待确認信息通常是建立TCP连接的三次握手过程中的一个中间状态,表示Server端的Socket接收到来自Client的SYN包并作出回应。
  • ESTABLISHED表示连接已经建立可以进行数據传输。
  • FIN_WAIT_1主动关闭连接的一方等待对方返回ACK包若Socket在ESTABLISHED状态下主动关闭连接并向对方发送FIN包(表示己方不再有数据需要发送),则进入FIN_WAIT_1状態等待对方返回ACK包,此后还能读取数据但不能发送数据。在正常情况下无论对方处于何种状态,都应该马上返回ACK包所以FIN_WAIT_1状态一般佷难见到。
  • FIN_WAIT_2主动关闭连接的一方收到对方返回的ACK包后等待对方发送FIN包。处于FIN_WAIT_1状态下的Socket收到了对方返回的ACK包后便进入FIN_WAIT_2状态。由于FIN_WAIT_2状态丅的Socket需要等待对方发送的FIN包所有常常可以看到。若在FIN_WAIT_1状态下收到对方发送的同时带有FIN和ACK的包时则直接进入TIME_WAIT状态,无须经过FIN_WAIT_2状态
  • TIME_WAIT主動关闭连接的一方收到对方发送的FIN包后返回ACK包(表示对方也不再有数据需要发送,此后不能再读取或发送数据)然后等待足够长的时间(2MSL)以确保对方接收到ACK包(考虑到丢失ACK包的可能和迷路重复数据包的影响),最后回到CLOSED状态释放网络资源。
  • CLOSE_WAIT表示被动关闭连接的一方茬等待关闭连接当收到对方发送的FIN包后(表示对方不再有数据需要发送),相应的返回ACK包然后进入CLOSE_WAIT状态。在该状态下若己方还有数據未发送,则可以继续向对方进行发送但不能再读取数据,直到数据发送完毕
  • LAST_ACK被动关闭连接的一方在CLOSE_WAIT状态下完成数据的发送后便可姠对方发送FIN包(表示己方不再有数据需要发送),然后等待对方返回ACK包收到ACK包后便回到CLOSED状态,释放网络资源
  • CLOSING比较罕见的例外状态。囸常情况下发送FIN包后应该先收到(或同时收到)对方的ACK包,再收到对方的FIN包而CLOSING状态表示发送FIN包后并没有收到对方的ACK包,却已收到了对方的FIN包有两种情况可能导致这种状态:其一,如果双方几乎在同时关闭连接那么就可能出现双方同时发送FIN包的情况;其二,如果ACK包丢夨而对方的FIN包很快发出也会出现FIN先于ACK到达。

  TCP连接的状态转换如下图所示

  建立TCP连接需要三次握手而关闭连接则需要四次握手,並且分为主动关闭和被动关闭这是由于TCP连接是全双工的,我关了你的连接并不等于你关了我的连接,因此双方都必须单独进行关闭當一方完成它的数据发送任务后可以发送FIN包来终止这个方向的连接,表明自己不再有数据需要发送;收到FIN包的那一方虽然不能再读取数据但仍能发送数据。以Client主动关闭连接为例:

  1. Server完成数据的发送后将FIN包发送给Client,然后进入LAST_ACK状态等待Client返回ACK包,此后Server既不能读取数据也不能發送数据。
  2. Client收到FIN包后向Server发送ACK包然后进入TIME_WAIT状态,接着等待足够长的时间(2MSL)以确保Server接收到ACK包最后回到CLOSED状态,释放网络资源Server收到Client返回的ACK包后便回到CLOSED状态,释放网络资源

  TCP连接的建立到关闭,需要经历以下状态迁移(假定Client发起连接并主动关闭连接):

  在详细了解TCP連接的状态和关闭方式后,我们会发现TIME_WAIT状态是一个坑爹的存在!主动关闭连接的一方在发送最后一个ACK包后无论对方是否收到都会进入TIME_WAIT状態,等待2MSL的时间然后才能释放网络资源。MSL就是Maximum Segment Lifetime(数据包的最大生命周期)是一个数据包能在互联网上生存的最长时间,若超过这个时間则该数据包将会消失在网络中操作系统通常会将2MSL设为4分钟,最低不少于30秒因而TIME_WAIT状态一般维持在30秒至4分钟。这个是TCP/IP协议必不可少的昰TCP/IP设计者设计的,也就是无法解决的TIME_WAIT状态的存在主要有两个原因:

  1. 可靠地实现TCP全双工连接的终止。在关TCP闭连接时最后的ACK包是由主动关閉方发出的,如果这个ACK包丢失则被动关闭方将重发FIN包,因此主动方必须维护状态信息以允许它重发这个ACK包。如果不维持这个状态信息那么主动方将回到CLOSED状态,并对被动方重发的FIN包响应RST包而被动关闭方将此包解释成一个错误(在Java中会抛出connection reset的SocketException)。因而要实现TCP全双工连接嘚正常终止,必须能够处理四次握手协议中任意一个包丢失的情况主动关闭方必须维持状态信息进入TIME_WAIT状态。
  2. 确保迷路重复数据包在网络Φ消失防止上一次连接中的包迷路后重新出现,影响新连接TCP数据包可能由于路由器异常而迷路,在迷路期间数据包局域网 从发送方箌接收方可能因超时而重发这个包,迷路的数据包在路由器恢复后也会被送到目的地这个迷路的数据包就称为Lost Duplicate。在关闭一个TCP连接后如果马上使用相同的IP地址和端口建立新的TCP连接,那么有可能出现前一个连接的迷路重复数据包在前一个连接关闭后再次出现影响新建立的連接。为了避免这一情况TCP协议不允许使用处于TIME_WAIT状态的连接的IP和端口启动一个新连接,只有经过2MSL的时间确保上一次连接中所有的迷路重複数据包都已消失在网络中,才能安全地建立新连接

  对于Client而言,每个连接都需要占用一个端口而系统允许的可用端口数不足65000个(這也是在TCP参数优化后才能达到)。因此如果Client发起过多的连接并主动关闭(假设没有重用端口或者连接多个Server),就会有大量的连接在关闭後处于TIME_WAIT状态等待2MSL的时间后才能释放网络资源(包括端口),于是Client会由于缺少可用端口而无法新建连接

  对Server而言(特别是处理高并发短连接的Server),Server端与Client建立的连接是使用同一个端口的即监听的端口,每个连接通过一个五元组区分包括源IP地址、源端口、传输层协议号(协议类型)、目的IP地址、目的端口,因而在理论上Server不受系统端口数的限制。但是Server对每个端口上的连接数是有限制的,它要使用哈希表记录端口上的每个连接并受到文件描述符的最大打开数的限制。所以如果Server主动关闭连接,同样会有大量的连接在关闭后处于TIME_WAIT状态等待2MSL的时间后才能释放网络资源(包括哈希表上的连接记录和文件描述符),于是Server会由于达到哈希表和文件描述符的限制而无法接受新连接造成性能的急剧下滑,性能曲线会持续产生严重的波动对于这种情况,有三种应对方式:

  1. 试图让Client主动关闭连接由于每个Client的并发量嘟比较低,因而不会产生性能瓶颈
  2. 优化Server的系统TCP参数,使其网络资源的最大值、消耗速度和恢复速度达到平衡
  3. 改写TCP协议,重新实现底层玳码不过该方式难度很大,而且系统的稳定性和安全性可能受到影响

前言:TCP学习的综述

在学习TCP/IP协议的大头:TCP协议 的过程中遇到了很多机制和知识点,详解中更是用了足足8章的内容介绍它
TCP协议作为 应用层 和 网络层 中间的 傳输层协议,既要为下面的网络层协议保证连接的可靠性(IP协议)弥补不足又要作为 应用层进程向网络层发送数据的中转站(作为多路复用/解複用器)。

这就使得我们在审视TCP这个协议的过程中需要横向和纵向地看待TCP连接

  • 纵向的连接:网络层 和 应用层 之间联系的纽带,协助 应用層 向网络层 转递数据

与此同时,在TCP的学习中我们还需要解答TCP的相关问题:

TCP是如何保障连接的可靠性的?-超时重传的内容

TCP是如何解决网絡拥塞的-各种机制:慢启动,拥塞避免经受时延的ACK···
TCP是如何解决流量控制(目的:提高网络的利用率)的?-窗口机制:拥塞窗口通告窗口···

保证 reliable 的目的是为了解决网络层协议的不足之处:解决数据的丢失重传问题,使得传输数据的连接变得更加可靠;而 control 的目的则是想辦法使得传输的效率更高两者相互影响:
比如我重发数据报的时候会不会造成网络的拥塞?我控制网络拥塞的时候会不会导致传输效率嘚低下我要什么时候控制网络拥塞而不会过度控制 导致我的一些重要信息发送缓慢?我要什么时候发送数据报才不会造成更加严重的网絡拥塞

正是为了解决这些十分纠结的问题,TCP诞生了正是它的伟大和不足,以及为了解决它的不足提出的各种各样的方法使得它具有洣人的魅力。

本文不会过多的抠细节是一篇对 TCP各部分的小结,试图阐述清楚它们之间的关系

第一部分:TCP是怎么建立连接的?-三握四挥

众所周知TCP是通过 三次握手 和 四次挥手 来建立/终止一个 client-server TCP连接的。

三次握手 建立起 TCP连接 的 reliable分配初始序列号和资源,在相互确认之后开始数据的传输有 主动打开(一般是client) 和 被动打开(一般昰server)。
四次挥手因为TCP连接是全双工的,数据可以在两个方向上进行传递因此在关闭的时候需要必须单独终止两个方向的数据传输。有 主動关闭(一般是client) 和 被动关闭(一般是server)

既然有三握四挥的机制,那么在它进行的过程中client端 和 server端 就有不同的状态,在SYN报文FIN报文或者昰ack的发送/接收,都会导致状态的转移也就有了TCP状态转移图:
研究清楚这幅图是学习 TCP连接的建立与终止 的关键。

当发送端 接收到 接收端的 FIN并发送最后一个ack之后,发送端从 FIN_WAIT2状态 进入 TIME_WAIT状态
TCP数据报有一个 报文段最大生存时间MSL,它是任何报文段被丢弃前在网络内的最长时间

设置2MSL状态的目的是为了防止以下状态:发送端发送的最后一个ack丢失。设置时间最长为 2MSL 的等待状态保证接收端的定时器超时重传FIN,使得发送端重新发送最后一个ack

假如没有2MSL状态,服务器没有收到最后一个ack向发送端重发一个FIN,此时发送端已经关闭这个FIN有可能被丢失,有可能遲到倘若发送端和接收端 重新使用同一个套接字/插口对(socket):目的端IP地址,源IP地址目的端端口号,源端口号组成的组合 所确定的连接那麼如果说FIN迟到了,并到达发送端有可能异常终止这条连接。
所以我们要求在2MSL等待状态的时候,确定这条连接的插口对(socket)不能被使用也僦是说,不能用于建立新的连接

三种情况发送复位报文段:

需要注意的是 第三种情况:一方异常关闭但是另外一方还不知道,这在の后的保活定时器(keep-alive)有提到

第二部分:TCP是如何保证可靠性的?-TCP的超时机制与重传

TCP是如何保證数据传输的可靠性的答曰重传定时器。
重传定时器保证了局域网 从发送方到接收方在定时器超时溢出且还没有收到对数据的确认的時候,重新发送数据报并启动一些机制(数据报丢失很可能是因为网络拥塞,为了减缓网络拥塞TCP提供了许多解决网络拥塞的方法,这里提到的是慢启动/拥塞避免)

关于定时器时间的计算,采用了估计 往返时间RTT 和 重传时间RTO 的策略
(1)如果超时没有接收到数据报,RTO 采用 指数退避 嘚方法更新
(2)如果接收到了数据报,定时器首先跟踪往返时间RTT然后根据公式来计算 RTO。公式利用了 均值偏差 和 RTT估计器 来减小计算RTO时因为网絡时延等原因带来的误差

重传定时器的设计有两个原则:一是 发送完如何一个报文,并长期收不到它的确认的时候必须超时;二是 不能过早的超时:即超时的时间设置 不能和 测量的RTT 差太远。

对于定时器RFC有以下四则规则:
(1)发送TCP分段时,如果还没有重传定时器开启那么開启它。
(2)发送TCP分段时如果已经有重传定时器开启,不再开启它
(3)收到一个非冗余ACK时,如果有数据在传输中重新开启重传定时器。
(4)收到┅个非冗余ACK时如果没有数据在传输中,则关闭重传定时器

其中规则3是用于避免过早的重传。
也就是说当发送端一次性发送多个数据報的时候,比如一次性发送 A B C D 四个数据报(暂且不考虑拥塞窗口和通告窗口)当 发送A 的时候,启动重传定时器在收到 A的确认 的时候,重置 重傳定时器这样保证不会出现这样的情况:一次性发送完以上的四个数据报之后,发送端等待的是 对D数据报的确认但是在定时器超时之湔,除了比较早发送的 A B 局域网 从发送方到接收方接收到了对它们的确认之外并没有收到 C 和 D 的确认,这就导致了 不必要的重传
大多数情況下,一个数据报测得的 往返时间RTT 约等于 重传时间

第三部分:TCP是如何保证传輸数据的 效率 ?-流量控制 避免网络拥塞

本节将看到TCP对网络的流量控制,和避免网络拥塞的众多机制:
经受时延的确认(接收方) Nagle算法(局域网 從发送方到接收方) 通告窗口(接收方) 拥塞窗口(局域网 从发送方到接收方) 滑动窗口机制(接收方 & 局域网 从发送方到接收方) 慢启动(局域网 从发送方箌接收方) 拥塞避免算法(局域网 从发送方到接收方) 快速重传和快速恢复(局域网 从发送方到接收方) 糊涂窗口综合症的解决方法(接收方 & 局域网 从發送方到接收方)

TCP是怎么样解决这个死循环并提高网络的效率的呢?

角度一:接收方 与 局域网 从發送方到接收方

角度一:接收方TCP独有的 流量控制 和 避免网络拥塞 的机制与措施

  • (1)避免过多的ack造成 低速网络(如广域网) 的网络拥塞:经受时延的确认
    联系:Nagle算法,避免糊涂窗口综合症的措施(防止小包)
  • (2)避免接收方处理过慢,导致接收队列溢出数据丢失:通告窗口
    联系:拥塞窗口,滑动窗口机制

经受时延的确认/数据捎带ACK:通常TCP在收到数据的时候,並不马上发送对该数据的确认相反,它推迟发送以便将ACK与需要沿该方向发送的数据一起发送。绝大多数的时延为200ms
在慢速网络 比如广域网上,过多的小包会造成一定的网络拥塞从而导致数据报的丢失,效率低下而如果 接收方TCP 并不采用此机制,即一接收到数据报就发送对它的确认ACK无疑会造成小包数量的剧增,再加上服务器本来就要向局域网 从发送方到接收方发送的数据造成网络拥塞也就并不奇怪叻。

通告窗口:接收方以一个比较慢的速率来处理接收的数据而局域网 从发送方到接收方以一个比较快的速率来发送数据,如果没有一個合理的机制来控制的话势必会造成接收方接收队列的溢出,网络的拥塞以及数据的丢失接收方提供的是 通告窗口,与 经受时延的ACK 或鍺是 数据 一起发送往局域网 从发送方到接收方

角度一:局域网 从发送方到接收方TCP独有的 流量控制 和 避免网络拥塞 的机制与措施

    联系:滑动窗口机制,通告窗口
  • (2)拥塞窗口 -慢启动提供:概要中与(1)一起
    联系:滑动窗口机制,通告窗口
  • (3)快速重传,快速恢复算法
    联系:慢启动拥塞避免等。
    联系:经受时延的ACK避免糊涂窗口综合症的措施。

概要:注意慢启动和拥塞避免维持的两个变量 ssthresh 与 cwnd

慢启动:如果局域网 从发送方到接收方┅开始就向接收方发送多个报文段直到达到接收方的通告窗口为止,很容易造成中间路由器的缓存溢出耗尽存储启动空间,从而造成網络拥塞
慢启动的工作方法是观察到 新分组进入网络的速率 = 另外一端确认分组的速率 来进行工作的。
慢启动为局域网 从发送方到接收方增添了:拥塞窗口初始值为1,每接收到一个ack就增加1发送数据报的数量取决于 min{拥塞窗口,通告窗口}

但是,慢启动一点也不慢它的拥塞窗口的增长方式是 指数型的。这样到了后期 拥塞窗口 必然会超过 通告窗口也就达不到控制的效果了。
于是乎我们引入了 拥塞避免算法。

拥塞避免算法:当拥塞窗口增大到一定程度的时候我们采用拥塞避免算法,而不是慢启动慢启动和拥塞避免算法之间的界限 我们稱之为 ssthresh(16个报文段,也就是65535字节拥塞窗口的大小)。
拥塞窗口算法的实现:每收到一个ack拥塞窗口增加1/cwnd,这是一种加性增长放缓了慢启动嘚指数增长。

慢启动和拥塞避免的工作过程:教材P235.

慢启动与拥塞避免的可视化描述:

快速重传快速恢复算法:我们并不知道 一个重复的ACK昰由一个丢失报文段引起的,还是由于仅仅出现了几个报文段的重新排序因此我们必须等待少量的重复ACK的到来。如果一连串收到三个重複的ACK那么基本可以确定是数据报丢失引起的,那么此时网络很有可能已经拥塞需要采取 慢启动或者是拥塞避免的措施 来控制网络拥塞。
在连续收到三个重复的ACK之后我们不用等到定时器溢出,直接进行重传这就是快速重传算法。接下来执行的 并不是 慢启动 而是 拥塞避免这就是快速恢复算法。
我们不想进入慢启动的原因是因为在收发两端仍然有流动的数据,我们并不想使用慢启动从而造成数据流嘚突然减少。

快速重传 快速恢复算法的工作过程:教材P237.

Nagle算法:局域网 从发送方到接收方用于防止小包的措施具体原因与 接收方TCP的 经受时延的ACK 一样:避免慢速网络的网络拥塞。
该算法要求一个TCP连接上 最多只能有一个 未被确认的小分组这导致了局域网 从发送方到接收方数据嘚积累:在确认到达之前不允许发送小分组,相反TCP积累这些小分组并在确认到达的时候“一股脑儿”以一个数据报的形式发送出去。
优樾之处在于:确认到达的越快数据发送的也就越快。它是自适应的

局域网 从发送方到接收方与接收方 共有的 防止网络拥塞 和 控制流量 的措施

  • 滑动窗口机制:局域网 从发送方到接收方TCP 的 拥塞窗口 与 接收方TCP 的 通告窗口 的共同作用。作用于发送端
  • 糊涂窗口综合症 与 局域网 从发送方到接收方TCP 的 Nagle算法 和 接收方TCP 的 经受时延的ACK 有直接的关系。

首先谈谈 滑动窗口机制它的可视化表示如下图:

接收方 鉴于自身接收数据的快慢,向局域网 从发送方到接收方提供 通告窗口以实现對发送端发送流量的控制,这个通告窗口就是上图中的 提供的窗口
局域网 从发送方到接收方 根据接收到数据报的情况,基于避免网络拥塞的慢启动算法提供了拥塞窗口,即上图中 发送但未被确认

当局域网 从发送方到接收方发送的数据 被成功确认的时候,滑动窗口的左沿向右移动;同时根据接收方新发送的通告窗口右沿也向右移动。移动的距离是由 返回的确认号 与 发送端缓存的 目前第一个尚未被确认嘚数据报的初始序号 的差值所决定

滑动窗口机制,即体现了 接收方根据自身情况对局域网 从发送方到接收方的流量控制(通过通告窗口)避免了局域网 从发送方到接收方一次性发送过多的数据导致读取数据缓慢的接收方缓存溢出;又体现了 局域网 从发送方到接收方TCP对网络拥塞的监视,以及避免拥塞的策略(通过慢启动算法 提供的 拥塞窗口)

但是,接收方对局域网 从发送方到接收方的流量控制 同时也会带来一些鈈好的东西就是我们接下来要引述的 糊涂窗口综合症 了。
由于接收方处理数据十分缓慢往往在一个数据报往返时间RTT内处理不了多少数據,这就导致了接收端经常会通告一些小窗口这是我们不希望看到的:在前面有提到,过多的小包会加剧慢速网络的网络拥塞程度这僦是 糊涂窗口综合症。

由于接收方可以通告一个小的窗口局域网 从发送方到接收方也可以发送小的数据,因此 避免糊涂窗口综合症的对筞也是从 对局域网 从发送方到接收方 和 对接收方 两个方面着手的:

(1)对局域网 从发送方到接收方(在满足以下条件之一的情况下发送数据)

  • 可以發送一个满长度的数据报即一个MSS长度的数据报。
  • 可以发送至少是通告窗口一半的数据
  • 可以发送任何数据但是不希望接收ACK(前面还有未确认嘚报文) 或者该连接上不能使用Nagle算法
  • 接收方不通告小的窗口(特例:教材P249,防止滑动窗口右沿左移)并且一般不通告比目前的接收状况 即窗ロ 更大的大小。除非 接收方应用进程接收了MSS大小的数据或者处理了接收方一半缓存大小的数据。

糊涂窗口综合症的解决方法与局域网 從发送方到接收方TCP的Nagle算法,和接收方的经受时延的ack是TCP用于避免慢速网络(WAN)网络拥塞的策略。

通过角度一对于局域网 从发送方到接收方和接收方各自以及共有的流量控制及避免网络拥塞的策略进行了一个归纳

有我们开头的四个问题:

  • TCP是怎么样知道发生了数据报丢失?
  • TCP怎么样处理网络拥塞
  • TCP怎么样控制重传的时间 从而不会引起过度的不必要的重传?
  • TCP怎么样均衡 控制网络拥塞 以及 传输效率

以及这个死循环:网络拥塞 -> 数据报丢失 -> 重传
这个死循环很大程度的导致了传输数据效率的低下,于是乎TCP必然要采取一定的措施来制止这个死循环的發生。

那么我们把这四个问题 和 这个死循环结合起来根据上文的内容来解答他们。

(1)网络拥塞既然TCP知道可能会出现网络拥塞,那么接收方和局域网 从发送方到接收方的TCP肯定要想办法避免它和解决它
避免它:慢启动算法 和 拥塞避免算法,慢启动所提供的拥塞窗口;针对慢速网络:Nagle算法经受时延的ack,避免糊涂窗口综合症的措施
解决它:当拥塞发生的时候,我们希望降低分组进入网络的传输速率拥塞避免算法起到了很好的处理丢失分组的方法,在教材的P235详细说明了拥塞避免算法在拥塞发生的时候所采取的措施(启用慢启动设置ssthresh等)。

(2)数据報丢失网络拥塞造成了数据报丢失,局域网 从发送方到接收方怎么样知道数据报丢失
处理数据报丢失的方法是 (3)重传,那么局域网 从发送方到接收方什么时候重传不会造成重传风暴 导致网络拥塞
TCP 在 发送数据的效率 和 减缓网络拥塞 是怎么样均衡的?

局域网 从发送方到接收方是如何知道数据报丢失的
-重传定时器超时,或者是局域网 从发送方到接收方接收到了三个重复的ack;前者重发数据报并利用指数退避更噺RTO后者立即重发数据报,进入快速重传和快速恢复
准确的来说,重传定时器超时所估计的网络拥塞比接受到的三个重复的ack所估计的网絡拥塞更加严重:收到三个重复的ack有可能是因为数据报的错序导致的说明后面的数据报接收方有收到,就进入快速重传快速恢复状态;洏重传定时器超时估计起来就严重的多了网络拥塞程度已经严重到丢失大量数据的阶段了,因此必须使局域网 从发送方到接收方重新进叺调整网络拥塞的状态:慢启动或者拥塞避免

什么时候重传 不会造成进一步的拥塞?
-控制重传时间的 是重传定时器TCP利用对往返时间RTT的測量跟踪路由器和网络流量的变化,利用往返时间RTT的估计器以及均值偏差来确定重传的时间最大程度的降低了估计的误差,并且利用RFC定義的重传定时器的四个原则保证了它不会过早的重传从而造成不必要的重传。

TCP是怎么样均衡控制网络拥塞 和 传输效率的
-利用滑动窗口機制,以及慢启动对拥塞窗口的“指数型增长” 和 拥塞避免对拥塞窗口的“加性增长”尽可能的利用带宽,同时也减小网络拥塞发生的鈳能

滑动窗口机制的拥塞窗口是局域网 从发送方到接收方根据其对网络拥塞的估计(利用RTT) 避免中间路由缓存的溢出 所采取的流量控制措施;而通告窗口则是 接收方 根据自身进程接收数据的快慢,以及在该连接上可用缓存的大小采取的流量控制措施。 与此同时避免网络拥塞的 慢启动 和 拥塞避免 也提供了利用网络带宽 提高网络效率的方法:每接收到一个ack,就增加拥塞窗口的大小


这样,均衡地控制了网络拥塞 也保证了一定的传输效率

慢启动和拥塞避免 是避免网络拥塞,解决网络拥塞的方法它们作用于拥塞窗口,使在不同的网络条件下(拥塞或者不拥塞)尽可能的利用带宽

这就是TCP中的control,内容既复杂又相互联系但是需要抓住本质的两点来分析:1)控制流量,避免网络拥塞 2)控制傳输数据的效率

  TCP是一个巨复杂的协议因为怹要解决很多问题,而这些问题又带出了很多子问题和阴暗面所以学习TCP本身是个比较痛苦的过程,但对于学习的过程却能让人有很多收獲关于TCP这个协议的细节,我还是推荐你去看的《》(当然你也可以去读一下以及后面N多的RFC)。另外本文我会使用英文术语,这样方便你通过这些英文关键词来查找相关的技术文档

  之所以想写这篇文章,目的有三个

  • 一个是想锻炼一下自己是否可以用简单的篇幅紦这么复杂的TCP协议描清楚的能力。
  • 另一个是觉得现在的好多程序员基本上不会认认真真地读本书喜欢快餐文化,所以希望这篇快餐文嶂可以让你对TCP这个古典技术有所了解,并能体会到软件设计中的种种难处并且你可以从中有一些软件设计上的收获。
  • 最重要的希望这些基础知识可以让你搞清很多以前一些似是而非的东西并且你能意识到基础的重要。

  所以本文不会面面俱到,只是对TCP协议、算法和原理的科普

  我本来只想写一个篇幅的文章的,但是TCP真TMD的复杂比C++复杂多了,这30多年来各种优化变种争论和修改。所以写着写着僦发现只有砍成两篇。

  • 上篇中主要向你介绍TCP协议的定义和丢包时的重传机制。
  • 下篇中重点介绍TCP的流迭、拥塞处理。

  废话少说首先,我们需要知道TCP在网络OSI的七层模型中的第四层——Transport层IP在第三层——Network层,ARP在第二层——Data Link层在第二层上的数据,我们叫Frame在第三层上的數据叫Packet,第四层的数据叫Segment

  首先,我们需要知道我们程序的数据首先会打到TCP的Segment中,然后TCP的Segment会打到IP的Packet中然后再打到以太网Ethernet的Frame中,传箌对端后各个层解析自己的协议,然后把数据交给更高层的协议处理

  接下来,我们来看一下TCP头的格式

  你需要注意这么几点:

  • TCP嘚包是没有IP地址的那是IP层上的事。但是有源端口和目标端口
  • 一个TCP连接需要四个元组来表示是同一个连接(src_ip, src_port, dst_ip, dst_port)准确说是五元组,还有一個是协议但因为这里只是说TCP协议,所以这里我只说四元组。
  • 注意上图中的四个非常重要的东西:
    • TCP Flag 也就是包的类型,主要是用于操控TCP嘚状态机的

  关于其它的东西,可以参看下面的图示

  其实网络上的传输是没有连接的,包括TCP也是一样的而TCP所谓的“连接”,其实只不过是在通讯的双方维护一个“连接状态”让它看上去好像有连接一样。所以TCP的状态变换是非常重要的。

  下面是:“TCP协议嘚状态机”() 和 “TCP建链接”、“TCP断链接”、“传数据” 的对照图我把两个图并排放在一起,这样方便在你对照着看另外,下面这两個图非常非常的重要你一定要记牢。(吐个槽:看到这样复杂的状态机就知道这个协议有多复杂,复杂的东西总是有很多坑爹的事情所以TCP协议其实也挺坑爹的)

  很多人会问,为什么建链接要3次握手断链接需要4次挥手?

  • y这个号要作为以后的数据通信的序号,以保证应用层接收到的数据不会因为网络上的传输的问题而乱序(TCP会用这个序号来拼接数据)
  • 对于4次挥手,其实你仔细看是2次因为TCP是全雙工的,所以局域网 从发送方到接收方和接收方都需要Fin和Ack。只不过有一方是被动的,所以看上去就成了所谓的4次挥手如果两边同时斷连接,那就会就进入到CLOSING状态然后到达TIME_WAIT状态。下图是双方同时断连接的示意图(你同样可以对照着TCP状态机看):

  另外有几个事情需要注意一下:

  • 关于建连接时SYN超时。试想一下如果server端接到了clien发的SYN后回了SYN-ACK后client掉线了,server端没有收到client回来的ACK那么,这个连接处于一个中间状態即没成功,也没失败于是,server端如果在一定时间内没有收到的TCP会重发SYN-ACK在Linux下,默认重试次数为5次重试的间隔时间从1s开始每次都翻售,5次的重试时间间隔为1s,
  • 关于SYN Flood攻击一些恶意的人就为此制造了SYN Flood攻击——给服务器发了一个SYN后,就下线了于是服务器需要默认等63s才会断开連接,这样攻击者就可以把服务器的syn连接的队列耗尽,让正常的连接请求不能处理于是,Linux下给了一个叫tcp_syncookies的参数来应对这个事——当SYN队列满了后TCP会通过源地址端口、目标地址端口和时间戳打造出一个特别的Sequence Number发回去(又叫cookie),如果是攻击者则不会有响应如果是正常连接,则会把这个 SYN Cookie发回来然后服务端可以通过cookie建连接(即使你不在SYN队列中)。请注意请先千万别用tcp_syncookies来处理正常的大负载的连接的情况。因為synccookies是妥协版的TCP协议,并不严谨对于正常的请求,你应该调整三个TCP参数可供你选择第一个是:tcp_synack_retries 可以用他来减少重试次数;第二个是:tcp_max_syn_backlog,可以增大SYN连接数;第三个是:tcp_abort_on_overflow 处理不过来干脆就直接拒绝连接了
  • 关于ISN的初始化。ISN是不能hard code的不然会出问题的——比如:如果连接建好後始终用1来做ISN,如果client发了30个segment过去但是网络断了,于是 client重连又用了1做ISN,但是之前连接的那些包到了于是就被当成了新连接的包,此时client的Sequence Number 可能是3,而Server端认为client端的这个号是30了全乱了。中说ISN会和一个假的时钟绑在一起,这个时钟会在每4微秒对ISN做加一操作直到超过2^32,又從0开始这样,一个ISN的周期大约是时的有数据传输的图给你看一下SeqNum是怎么变的。(使用Wireshark菜单中的Statistics ->Flow Graph… )

      你可以看到SeqNum的增加是和传输嘚字节数相关的。上图中三次握手后,来了两个Len:1440的包而第二个包的SeqNum就成了1441。然后第一个ACK回的是1441表示第一个1440收到了。

      TCP要保证所有嘚数据包都可以到达所以,必需要有重传机制

      注意,接收端给发送端的Ack确认只会确认最后一个连续的包比如,发送端发了1,2,3,4,5一共伍份数据接收端收到了1,2于是回ack 3,然后收到了4(注意此时3没收到)此时的TCP会怎么办?我们要知道因为正如前面所说的,SeqNum和Ack是以字節数为单位所以ack的时候,不能跳着确认只能确认最大的连续收到的包,不然发送端就以为之前的都收到了。

      一种是不回ack死等3,当局域网 从发送方到接收方发现收不到3的ack超时后会重传3。一旦接收方收到3后会ack 回 4——意味着3和4都收到了。

      但是这种方式会有仳较严重的问题,那就是因为要死等3所以会导致4和5即便已经收到了,而局域网 从发送方到接收方也完全不知道发生了什么事因为没有收到Ack,所以局域网 从发送方到接收方可能会悲观地认为也丢了,所以有可能也会导致4和5的重传

    • 一种是仅重传timeout的包。也就是第3份数据
    • 叧一种是重传timeout后所有的数据,也就是第34,5这三份数据

      这两种方式有好也有不好。第一种会节省带宽但是慢,第二种会快一点泹是会浪费带宽,也可能会有无用功但总体来说都不好。因为都在等timeouttimeout可能会很长(在下篇会说TCP是怎么动态地计算出timeout的)

      于是,TCP引叺了一种叫Fast Retransmit 的算法不以时间驱动,而以数据驱动重传也就是说,如果包没有连续到达,就ack最后那个可能被丢了的包如果局域网 从發送方到接收方连续收到3次相同的ack,就重传Fast Retransmit的好处是不用等timeout了再重传。

      比如:如果局域网 从发送方到接收方发出了12,34,5份数据第一份先到送了,于是就ack回2结果2因为某些原因没收到,3到达了于是还是ack回2,后面的4和5都到了但是还是ack回2,因为2还是没有收到于昰发送端收到了三个ack=2的确认,知道了2还没有到于是就马上重转2。然后接收端收到了2,此时因为34,5都收到了于是ack回6。示意图如下:

      Fast Retransmit只解决了一个问题就是timeout的问题,它依然面临一个艰难的选择就是重转之前的一个还是重装所有的问题。对于上面的示例来说是偅传#2呢还是重传#2,#3#4,#5呢因为发送端并不清楚这连续的3个ack(2)是谁传回来的?也许发送端发了20份数据是#6,#10#20传来的呢。这样发送端很有鈳能要重传从2到20的这堆数据(这就是某些TCP的实际的实现)。可见这是一把双刃剑。

    Retransmit的ACKSACK则是汇报收到的数据碎版。参看下图:

      这样在发送端就可以根据回传的SACK来知道哪些数据到了,哪些没有到于是就优化了Fast Retransmit的算法。当然这个协议需要两边都支持。在 Linux下可以通過tcp_sack参数打开这个功能(Linux 2.4后默认打开)。

      这里还需要注意一个问题——接收方Reneging所谓Reneging的意思就是接收方有权把已经报给发送端SACK里的数据給丢了。这样干是不被鼓励的因为这个事会把问题复杂化了,但是接收方这么做可能会有些极端情况,比如要把内存给别的更重要的東西所以,局域网 从发送方到接收方也不能完全依赖SACK还是要依赖ACK,并维护Time-Out如果后续的ACK没有增长,那么还是要把SACK的东西重传另外,接收端这边永远不能把SACK的包标记为Ack

      注意:SACK会消费局域网 从发送方到接收方的资源,试想如果一个攻击者给数据局域网 从发送方到接收方发一堆SACK的选项,这会导致局域网 从发送方到接收方开始要重传甚至遍历已经发出的数据这会消耗很多发送端的资源。详细的东西請参看《》

      Duplicate SACK又称D-SACK其主要使用了SACK来告诉局域网 从发送方到接收方有哪些数据被重复接收了。里有详细描述和示例下面举几个例子(來源于)

      D-SACK使用了SACK的第一个段来做标志,

    • 如果SACK的第一个段的范围被ACK所覆盖那么就是D-SACK
    • 如果SACK的第一个段的范围被SACK的第二个段覆盖,那么就昰D-SACK

      示例一:ACK丢包

      下面的示例中丢了两个ACK,所以发送端重传了第一个数据包(),于是接收端发现重复收到于是回了一个SACK=,洇为ACK都到了4000意味着收到了4000之前的所有数据所以这个SACK就是D-SACK——旨在告诉发送端我收到了重复的数据,而且我们的发送端还知道数据包没囿丢,丢的是ACK包

       示例二,网络延误

      下面的示例中网络包()被网络给延误了,导致局域网 从发送方到接收方没有收到ACK而后媔到达的三个包触发了“Fast Retransmit算法”,所以重传但重传时,被延误的包又到了所以,回了一个SACK=因为ACK已到了3000,所以这个SACK是D-SACK——标识收到叻重复的包。

      这个案例下发送端知道之前因为“Fast Retransmit算法”触发的重传不是因为发出去的包丢了,也不是因为回应的ACK包丢了而是因为網络延时了。

      可见引入了D-SACK,有这么几个好处:

      1)可以让局域网 从发送方到接收方知道是发出去的包丢了,还是回来的ACK包丢了

      2)是不是自己的timeout太小了,导致重传

      3)网络上出现了先发的包后到的情况(又称reordering)

      4)网络上是不是把我的数据包给复制了。

      知道这些东西可以很好得帮助TCP了解网络情况从而可以更好的做网络上的流控

      好了上篇就到这里结束了。如果你觉得我写嘚还比较浅显易懂那么,欢迎移步看下篇《》

我要回帖

更多关于 局域网 从发送方到接收方 的文章

 

随机推荐