渴望明天
2021年高考,看到个图,挺好玩的,作为标题吧
哈哈哈 TCP
三次握手主要做什么
- 同步 初始序列号
- 同步 MSS
四次挥手一定是四次吗?
很容易抓到 三次挥手的包
MSS & MTU
尽可能避免IP的分片
网络通信,尽一切可能避免IP的分片!为什么?因为负责IP分片的那台主机、路由器会花费很多CPU资源来处理分片,同时负责重组IP分片的主机、路由器则需要更多的CPU资源来重组这些IP包的分片。比如一台路由器的数据处理能力是10G,如果处理分片则降低到2G左右,这还是比较高端的平台,采用硬件,软件CPU一起协作才达到的水平,如果比较低端的平台,纯粹采用CPU软件来处理,那数据包处理能力简直惨不忍睹!
MSS
一般网络接口都有一个最大传输单元即MTU,比如MTU=1500,什么意思呢?如果IP包的尺寸<= 1500,原封不动从这个网络接口发送,否则就需要分片。
那聪明的你一定会说,既然知道MTU=1500,那为什么要发超过1500的包呢?给自己找不自在?既然IP包最大1500了,那如果TCP=1480不就可以了吗?
1 | IP = IP Header + TCP = 20 + 1480 = 1500 |
这个TCP Payload = 1460 对应的就是TCP最大传输单元,我们称之为“ Maximum Segment Size” 即MSS,那么MSS= MTU - IP Header - TCP Header = 1500 - 20 -20 = 1460。
TCP 在三次握手的第一个SYN消息中有一个选项option 4,就是为了协商通信双方的MSS,如果一方MSS=1460,而另一方的MSS= 8960,会选择较小的一方即1460作为这个TCP连接的MSS,这样双向通信都可以避免因为IP包太大引起的分片。但是MSS能完全避免IP分片吗?不能!还有什么好的办法?
Path MTU Discovery
还是拿第一个例子来讲,IP包= 1500 从源主机发送出来了,然后在互联网上一跳一跳奔向目的地,突然到了一台路由器上,需要从一个接口发送出去,这个接口MTU只有1000,
由于IP包 1500 > 1000,这个IP包必须分片才能出去,于是就分成了两个IP分片发送出了,那能否让这台路由器不分片呢?
可以的,大家记得IP头有一个标志位DF,Don’t Fragment,如果为1,意思是这个IP包在传输的过程中不能分片,如果此IP包大于物理接口的MTU,请直接丢弃,
并发消息告诉源主机!什么消息?ICMP的消息,告诉包因为太大了,因为不能分片所以被丢了,并告诉源主机可以重新发小于等于MTU的包;那发什么样的ICMP消息?
ICMP协议里有type字段,还有code字段,发送type=3,code=4,MTU=1000的消息就可以了,当这个ICMP消息到达IP包的源主机,源主机知道原来是IP太大了,那最大可以发送多大的呢?ICMP消息里有,那就是MTU = 1000,于是源主机发送小于等于1000字节的就可以避免在传输路径上的分片。
如果IP包=1000在传输过程中遇到更小的MTU=500怎么办?呵呵,重复以上的步骤就可以了!这个不断寻找路径上最小MTU的过程,我们称之为:Path MTU Discovery。
如果ICMP type=3 code=4 无法到达源主机会发生什么?很显然IP包被静静地丢了,TCP连接超时中断。ICMP为什么回不来?一般被防火墙或者路由器的访问控制列表ACL给无情拒绝通过!如果你可以管理配置这些设备,只要允许ICMP type=3 code=4通过就可以了,否则只有老老实实关闭 Path MTU Discovery 这个功能了,至少分片可以通信,而不分片则彻底无法通信了,这就是聊胜于无,无奈的选择!
TIME_WAIT status
为何要有这个状态, 原因很简单,那就是每次建立连接的时候序列号都是随机产生的,并且这个序列号是32位的,会回绕。现在我来解释这和TIME_WAIT有什么关系。
任何的TCP分段都要在尽力而为的IP网络上传输,中间的路由器可能会随意的缓存任何的IP数据报,它并不管这个IP数据报上被承载的是什么数据,然而根据经验和互联网的大小,一个IP数据报最多存活MSL(这是根据地球表面积,电磁波在各种介质中的传输速率以及IP协议的TTL等综合推算出来的,如果在火星上,这个MSL会大得多…)。
现在我们考虑终止连接时的被动方发送了一个FIN,然后主动方回复了一个ACK,然而这个ACK可能会丢失,这会造成被动方重发FIN,这个FIN可能会在互联网上存活MSL。
如果没有TIME_WAIT的话,假设连接1已经断开,然而其被动方最后重发的那个FIN(或者FIN之前发送的任何TCP分段)还在网络上,然而连接2重用了连接1的所有的5元素(源IP,目的IP,TCP,源端口,目的端口),刚刚将建立好连接,连接1迟到的FIN到达了,这个FIN将以比较低但是确实可能的概率终止掉连接2.
为何说是概率比较低呢?这涉及到一个匹配问题,迟到的FIN分段的序列号必须落在连接2的一方的期望序列号范围之内。虽然这种巧合很少发生,但确实会发生,毕竟初始序列号是随机产生了。因此终止连接的主动方必须在接受了被动方且回复了ACK之后等待2*MSL时间才能进入CLOSE状态,之所以乘以2是因为这是保守的算法,最坏情况下,针对被动方的ACK在以最长路线(经历一个MSL)经过互联网马上到达被动方时丢失。
为了应对这个问题,RFC793对初始序列号的生成有个建议,那就是设定一个基准,在这个基准之上搞随机,这个基准就是时间,我们知道时间是单调递增的。然而这仍然有问题,那就是回绕问题,如果发生回绕,那么新的序列号将会落到一个很低的值。 因此最好的办法就是避开“重叠”,其含义就是基准之上的随机要设定一个范围。
要知道,很多人很不喜欢看到服务器上出现大量的TIME_WAIT状态的连接,因此他们将TIME_WAIT的值设置的很低,这虽然在大多数情况下可行,然而确实也是一种冒险行为。最好的方式就是,不要重用一个连接。
还有一个状态是 CLOSE_WAIT
CLOSE_WAIT 状态在服务器停留时间很短,如果你发现大量的 CLOSE_WAIT 状态,那么就意味着被动关闭的一方没有及时发出 FIN 包,这时候基本要去查代码了,代码不严谨,这个链接关不了了
参考: https://zhuanlan.zhihu.com/p/60382685
SACK
早期TCP实现为,只要一个TCP分段丢失,即使后面的TCP分段都被完整收到,发送端还是会重传从丢失分段开始的所有报文,这就会导致一个问题,那就是重传风暴,一个分段丢失,引起大量的重传。这种风暴实则不必要的,因为大多数的TCP实现中,接收端已经缓存了乱序的分段,这些被重传的丢失分段之后的分段到达接收端之后,很大的可能性是被丢弃。
TCP是保证数据顺序的,但是并不意味着它总是会丢弃乱序的TCP分段,具体会不会丢弃是和具体实现相关的,RFC建议如果内存允许,还是要缓存这些乱序到来的分段,然后实现一种机制等到可以拼接成一个按序序列的时候将缓存的分段拼接,这就类似于IP协议中的分片一样,但是由于IP数据报是不确认的,因此IP协议的实现必须缓存收到的任何分片而不能将其丢弃,因为丢弃了一个IP分片,它就再也不会到来了。
现在,TCP实现了一种称为 选择确认的方式,接收端会显式告诉发送端需要重传哪些分段而不需要重传哪些分段。这无疑避免了重传风暴。
延迟 ACK
大多数情况下,ACK还是可以和数据一起捎带传输的。如果没有捎带传输,那么就只能单独回来一个ACK,如果这样的分段太多,网络的利用率就会下降。从大同用火车拉到北京100吨煤,为了确认煤已收到,北京需要派一辆同样的火车空载开到大同去复命,因为没有别的交通工具,只有火车。如果这位复命者刚开着一列火车走,又从大同来了一车煤,这拉煤的哥们儿又要开一列空车去复命了。
RFC建议了一种延迟的ACK,也就是说,ACK在收到数据后并不马上回复,而是延迟一段可以接受的时间,延迟一段时间的目的是看能不能和接收方要发给发送方的数据一起回去,因为TCP协议头中总是包含确认号的,如果能的话,就将ACK一起捎带回去,这样网络利用率就提高了。往大同复命的确认者不必开一辆空载火车回大同了,此时北京正好有一批货物要送往大同,这位复命者搭着这批货的火车返回大同。
如果等了一段可以接受的时间,还是没有数据要发往发送端,此时就需要单独发送一个ACK了,然而即使如此,这个延迟的ACK虽然没有等到可以被捎带的数据分段,也可能等到了后续到来的TCP分段,这样它们就可以取最大者一起返回了,要知道,TCP的确认号是收到的按序报文的最后一个字节的后一个字节。最后,RFC建议,延迟的ACK最多等待两个分段的积累确认。
cwnd ( congestion window )
窗口分为滑动窗口和拥塞窗口。
滑动窗口是接受数据端使用的窗口大小,用来告知发送端接收端的缓存大小,以此可以控制发送端发送数据的大小,从而达到流量控制的目的。
当与另一个网络的主机建立TCP连接时,拥塞窗口被初始化为1个报文段(即另一端通告的报文段大小)。每收到一个ACK, 拥塞窗口就增加一个报文段(cwnd以字节为单位,但是慢启动以报文段大小为单位进行增加)。发送方取拥塞窗口与通告窗口中的最小值作为发送上限。拥塞窗 口是发送方使用的流量控制,而通告窗口则是接收方使用的流量控制。
发送方开始时发送一个报文段,然后等待ACK。当收到该ACK时,拥塞窗口从1增加为2,即可以发送两个报文段。当收到这两个报文段的ACK时,拥塞窗口就增加为4。这是一种指数增加的关系。
所谓快速重传/快速恢复是针对慢启动的,我们知道慢启动要从1个MSS开始增加拥塞窗口,而快速重传/快速恢复则是一旦收到3个冗余ACK,不必进入慢启动,而是将拥塞窗口缩小为当前阀值的一半加上3,然后如果继续收到冗余ACK,则将拥塞窗口加1个MSS,直到收到一个新的数据ACK,将窗口设置成正常的阀值,开始加性增加的阶段。