TCP:为了连接我真的是竭尽全力

{% note default %}
TCP很多东西很复杂,因为在复杂的网络世界里什么都是可能发生的,于是我们为了通信的可靠,在传输层设计一种十分可靠的传输协议来进行可靠的传输。

TCP天生相信网络世界是拥堵的

UDP之所以简单,是因为它像小孩子一样相信网络世界是美好的,发出去的包不会丢失,也不会拥堵,发出去也就不管了。
而实际上网络世界里很复杂,有很多复杂的情况。
TCP是大人了,所以它为了支持可靠的传输,费心费力。

TCP包头格式

源端口号和目的端口号是必不可少的。
除此之外,还有序号、确认序号、状态位、窗口大小、紧急指针等。

由此我们着重看TCP的以下问题:

  • 顺序问题
  • 丢包问题
  • 连接维护
  • 流量控制
  • 拥塞控制

TCP的三次握手

所有问题都是从连接开始的,TCP就是这样,要干什么事,先连接再说。

TCP为什么不是两次握手,又或者四次握手。为什么三次握手就够了呢。
首先不要被握手两个字迷惑了,以为是面对面连接呢?

在网络世界里,就像古时候信鸽传信一样,你发出去了啥问题都可能发生,你唯一知道结果的就是传回来的确认包是啥样的。
因此三次握手建立连接,我们双方都得是有去有回。也就是说彼此都能确定自己发的东西对方能收到

  1. 假设A要与B建立连接,A跟B发送一个SYN连接请求:B你在吗?实际情况就是A没收到B的回复会一直发送请求来骚扰
  2. B收到A的连接请求,回复一个ACK:我在。如果B不想理A,A骚扰一段时间后就走了
  3. 这时候A收到回复,就知道自己发的东西B能收到,那么单方面就建立连接状态了,但是B没有一去一回啊,所以还得给B发个确认的确认:你好呀,B。
    B收到确认的确认,也知道自己有去有回,也建立连接了。

实际上,确认的确认在网络里跑的时候也可能丢失,B一直收不到确认的确认不建立连接怎么办呢?B也可以反复发确认。事实上,大部分情况下,A收到回复后单方面建立连接了,会马上给B发数据,这个时候,就算确认的确认没办法到B,B收到A发送的数据,也会马上和A建立连接。

但是如果A就是无赖,建立连接后就是不发数据怎么办?作为服务端B,可以开启keepalive机制,又探活包,对于A这种长时间不发送数据的客户端,可以主动关闭连接。

TCP的四次挥手

不想玩了,A和B之间解散更加复杂,要四次挥手。

  1. A和B说:我不想玩了,你赶快干完你的事。之后A进入FIN-WAIT-1阶段
  2. B收到后,跟A说:哦,我知道了。之后B进入CLOSED-WAIT阶段。这个时候A收到B的回复之后会进入FIN-WAIT-2阶段
  3. B也不想玩了:A,我也不想玩了。之后B进入LAST-ACK阶段,等待A的最后回复
  4. A终于收到了B也不想玩的消息,愉快的发送:好的,拜拜。之后A进入TIME-WAIT阶段,等待一段时间后关闭。B如果收到了A的回复就会关闭。

其实也是有去有回的原理,但是关闭连接的过程涉及到工作的问题。那么双方各自来一段有去有回,也就是四次了。
A单方面不想玩了,跟B发了消息后,会进入FIN-WAIT-1阶段,B得给A发个回复,A收到后进入FIN-WAIT-2阶段,这个时候A不能马上关闭呀,因为B有它自己的工作,B还可能发送数据,只是这时候A可以选择接受不接受。
A这时候的状态就是我给你发送了我不想玩的消息,而且我确认你已经收到了,A的有去有回已经完成,就等B的不想玩了

终于B也不想玩了,跟A发:我也不想玩了。这时候B不能关闭,因为B的有去有回还没有完成。
A终于收到,奶奶个熊,你终于擦完你的屁股了,愉快的回复:好的,拜拜您嘞。注意这个时候A还是不能马上关闭,因为B可能收不到回复,B会重新发送B不玩了,这个时候A要重新发送回复,因此要等待一段时间。

另外A马上关闭还有一个问题就是,A的端口空出来了,这个时候B不知道,B原来发送的很多包可能还在路上,这个时候会送到别的占用端口的应用。就混乱了。

TCP的流量控制

一般来说,流量控制双方发送数据的速度与大小。
TCP的顺序问题,丢包问题,流量控制实际上都是通过一种叫滑动窗口的机制来实现的。
滑动窗口分为两种,发送窗口,与接收窗口。
滑动窗口通过指针来实现,把数据流分成了四个部分:

  • 已发送已确认
  • 已发送未确认
  • 未发送允许发送
  • 未发送不允许发送

其中如果出现了丢包问题,为了保证顺序以及数据的可靠性,会有超时重传的机制:

  • 自适应重传算法
    每一次遇到超时重传,会将超时时间隔设为先前的两倍,为了不在网络环境差的情况下不断重传。
  • 快速重传
    当接收方收到的是序号大于下一个期望的报文段时,会连续发送期望报文段序号上一个的ACK三次。客户端收到后会马上重传。
  • SACK
    这种方式就是在TCP头里加一个SACK的东西,可以将缓存的地图发送给发送方,这样发送方一下子就看出了哪个丢失了。
    流量控制的目的在于避免发送方发送数据将接收方的缓存塞满

TCP的拥塞控制

TCP的拥塞控制的目的在于为了不把网络塞满。
注意,拥塞控制不能避免出现拥塞的情况,只是为了在不丢包、不堵塞的情况下,尽可能得发挥带宽。

拥塞控制也是通过设置一个窗口值来实现的,只是叫拥塞窗口,是在把数据发送到网络中的层面上来讲的。
主要是四个方面:慢开始、拥塞避免、快重传、快恢复。

  • 慢开始:因为我根本不确定网络当中能够承受多少数据流,所以我最开始是慢慢往网络当中注入数据包,拥塞窗口值就是cwnd = 1。一收到一个ack就加一个窗口,那么cwnd就是指数增长。
  • 拥塞避免:这时候一达到门限值ssthresh,就开始拥塞控制算法。这时候一个RTT就加一,那么就是线性增长。
    拥塞的表现形式是丢包,也就是超时。那么这个时候就需要超时重传,这个时候就出现了拥塞。那么就把门限值ssthresh设为cwnd/2,同时cwnd设为1,重新开始慢开始。但是这种方式太暴力了,一下子回到解放前。
    所以可以采用:
    快重传快恢复:快重传呢,也就是连续发送前一个包的三次连续ACK,这个时候就把门限值ssthresh设为cwnd/2,但是cwnd=ssthresh,直接开始拥塞避免算法。

拥塞控制存在的问题

TCP就是用拥塞控制来知进知退,但是如果时延很重要的情况下,反而降低速度。
主要是两个问题:

  • 有时候丢包并不是拥塞的问题,可能就是网络本身的问题,这个时候可能网络并不拥塞,而这个时候选择退缩,是不合适的。
  • TCP的拥塞控制要等到讲中间设备都填充满了,出现丢包,从而降低速度,这个时候已经晚了。

针对这两个问题,后面有TCP BBR拥塞算法。试着找一个平衡点,不断地加快速度,但是不把中间设备的缓存填慢,这样努力达到高带宽和低时延的平衡。


posted @ 2022-01-06 23:53  Ryan~~~~  阅读(49)  评论(0)    收藏  举报