TCP:为了连接我真的是竭尽全力
{% note default %}
TCP很多东西很复杂,因为在复杂的网络世界里什么都是可能发生的,于是我们为了通信的可靠,在传输层设计一种十分可靠的传输协议来进行可靠的传输。
TCP天生相信网络世界是拥堵的
UDP之所以简单,是因为它像小孩子一样相信网络世界是美好的,发出去的包不会丢失,也不会拥堵,发出去也就不管了。
而实际上网络世界里很复杂,有很多复杂的情况。
TCP是大人了,所以它为了支持可靠的传输,费心费力。
TCP包头格式
源端口号和目的端口号是必不可少的。
除此之外,还有序号、确认序号、状态位、窗口大小、紧急指针等。
由此我们着重看TCP的以下问题:
- 顺序问题
- 丢包问题
- 连接维护
- 流量控制
- 拥塞控制
TCP的三次握手
所有问题都是从连接开始的,TCP就是这样,要干什么事,先连接再说。
TCP为什么不是两次握手,又或者四次握手。为什么三次握手就够了呢。
首先不要被握手两个字迷惑了,以为是面对面连接呢?
在网络世界里,就像古时候信鸽传信一样,你发出去了啥问题都可能发生,你唯一知道结果的就是传回来的确认包是啥样的。
因此三次握手建立连接,我们双方都得是有去有回。也就是说彼此都能确定自己发的东西对方能收到。
- 假设A要与B建立连接,A跟B发送一个SYN连接请求:B你在吗?实际情况就是A没收到B的回复会一直发送请求来骚扰
- B收到A的连接请求,回复一个ACK:我在。如果B不想理A,A骚扰一段时间后就走了
- 这时候A收到回复,就知道自己发的东西B能收到,那么单方面就建立连接状态了,但是B没有一去一回啊,所以还得给B发个确认的确认:你好呀,B。
B收到确认的确认,也知道自己有去有回,也建立连接了。
实际上,确认的确认在网络里跑的时候也可能丢失,B一直收不到确认的确认不建立连接怎么办呢?B也可以反复发确认。事实上,大部分情况下,A收到回复后单方面建立连接了,会马上给B发数据,这个时候,就算确认的确认没办法到B,B收到A发送的数据,也会马上和A建立连接。
但是如果A就是无赖,建立连接后就是不发数据怎么办?作为服务端B,可以开启keepalive机制,又探活包,对于A这种长时间不发送数据的客户端,可以主动关闭连接。
TCP的四次挥手
不想玩了,A和B之间解散更加复杂,要四次挥手。
- A和B说:我不想玩了,你赶快干完你的事。之后A进入FIN-WAIT-1阶段。
- B收到后,跟A说:哦,我知道了。之后B进入CLOSED-WAIT阶段。这个时候A收到B的回复之后会进入FIN-WAIT-2阶段。
- B也不想玩了:A,我也不想玩了。之后B进入LAST-ACK阶段,等待A的最后回复
- 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拥塞算法。试着找一个平衡点,不断地加快速度,但是不把中间设备的缓存填慢,这样努力达到高带宽和低时延的平衡。

浙公网安备 33010602011771号