TCP相关知识点
参考:
《计算机网络》
(建议收藏)TCP协议灵魂之问,巩固你的网路底层基础
关于 TCP 三次握手和四次挥手,满分回答在此 (值得看)
TCP处于网络体系结构中的运输层。
运输层主要为应用进程提供端到端的逻辑通信,然后对收到的报文进行差错检测等,它主要有两种不同的运输协议,即面向连接的TCP和无连接的UDP。
UDP
UDP 是无连接的,即发送数据之前不需要建立连接。
UDP 使用尽最大努力交付,即不保证可靠交付,同时也不使用拥塞控制。
UDP 是面向报文的。UDP 没有拥塞控制,很适合多媒体通信的要求。
UDP 支持一对一、一对多、多对一和多对多的交互通信。
UDP 的首部开销小,只有 8 个字节。
两个计算机中的进程要互相通信,不仅必须知道对方的 IP 地址(为了找到对方的计算机),而且还要知道对方的端口号(为了找到对方计算机中的应用进程)。
TCP
TCP主要的协议内容:连接管理、流量控制、拥塞控制、差错控制。
-
序号 seq:TCP 连接中传送的数据流中的每一个字节都按顺序编号(字节数)。序号字段的值则指的是本报文段所发送的数据的第一个字节的序号。
-
确认号ack:期望收到对方的下一个报文段的数据的第一个字节的序号。一般是序号+1。若确认号为 N,则表明:到序号 N-1 为止的所有数据都已正确收到。
-
数据偏移(首部长度):它指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远。这个字段实际上是指出TCP报文段的首部长度。
-
终止 FIN:用来释放一个连接。当 FIN = 1时,表明此报文段的发送发的数据已发送完毕,并要求释放运输连接。
-
RTT是一个报文自发送到接收端返回确认的时间。
连接建立
TCP报文中主要和连接有关的信息是SYN/ACK字段,SYN是同步位,表示这是一个连接请求或者连接接受报文,ACK是一个确认位,当ACK为1时确认号字段才有效。
TCP通过三次握手建立连接。
首先客户端向服务器发出连接请求报文段,其中同步位SYN为1,并生成随机序号 seq = x,表明传送数据时的第一个数据字节的序号是 x。
服务端接收请求后则返回确认报文,同步位SYN=1,确认位ACK=1,确认号(期待收到下一份报文的序号)为x+1,服务端同样生成随机序号为y。
客户端接收到之后知道服务器能接收到自己的信息,但是服务器仍不知道客户端收到没有,因此客户端再次发送第三份报文,同步位SYN=0,确认位ACK=1,确认号(通常为序号+1)y+1,序号(回应期待)x+1。
至此连接正式建立。
半连接队列
服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把这种状态下的请求连接放在一个队列里,我们把这种队列称之为半连接队列。
当然还有一个全连接队列,完成三次握手后建立起的连接就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。SYN 洪泛攻击
SYN 攻击就是 Client 在短时间内伪造大量不存在的 IP 地址,并向 Server 不断地发送 SYN 包,Server 则回复确认包,并等待 Client 确认,由于源地址不存在,因此 Server 需要不断重发直至超时,这些伪造的 SYN 包将长时间占用半连接队列,导致正常的 SYN 请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。如果第三次握手丢失了,客户端服务端会如何处理
服务器发送完 SYN-ACK 包,如果未收到客户端响应的确认包,也即第三次握手丢失。那么服务器就会进行首次重传,若等待一段时间仍未收到客户确认包,就进行第二次重传。如果重传次数超过系统规定的最大重传次数,则系统将该连接信> 息从半连接队列中删除。
注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s,2s,4s,8s…
四次挥手终止连接
这是由于 TCP 的半关闭(half-close)特性造成的,TCP 提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力,客户端或服务端均可主动发起挥手动作,TCP 连接的释放需要发送四个包(执行四个步骤),因此称为四次挥手。
1)第一次挥手:客户端发送一个 FIN 报文(请求连接终止:FIN = 1),报文中会指定一个序列号 seq = u。并停止再发送数据,主动关闭 TCP 连接。此时客户端处于 FIN_WAIT1 状态,等待服务端的确认。
FIN-WAIT-1 - 等待远程TCP的连接中断请求,或先前的连接中断请求的确认;2)第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。
CLOSE-WAIT - 等待从本地用户发来的连接中断请求;此时的 TCP 处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2(终止等待 2)状态,等待服务端发出的连接释放报文段。
FIN-WAIT-2 - 从远程TCP等待连接中断请求;3)第三次挥手:如果服务端也想断开连接了(没有要向客户端发出的数据),和客户端的第一次挥手一样,发送 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态,等待客户端的确认。
LAST-ACK - 等待原来发向远程TCP的连接中断请求的确认;4)第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答(ack = w+1),且把服务端的序列值 +1 作为自己 ACK 报文的序号值(seq=u+1),此时客户端处于 TIME_WAIT (时间等待)状态。
TIME-WAIT - 等待足够的时间以确保远程TCP接收到连接中断请求的确认;
简而言之:客户端主动关闭TCP连接,首先发送一个结束FIN报文,报文包括序列号x,等待服务端确认,此时客户端处于等待。服务端收到FIN报文后会发送ACK确认报文,并且确认号ack=x+1,序列号为服务器自定的y。此时TCP处于半关闭状态,客户端等待服务端发出的连接释放报文。服务端接收到连接关闭报文后可能需要一段时间处理,处理完毕后向刚才的客户端发送FIN报文,表示自己也可以关闭连接了,第三次发送的报文FIN=1,ACK=1,ack仍然为x+1(确认号同上一次),seq为任意序列号u。客户端接收到后发送ACK报文(ack=u+1,ACK=1,seq=x+1,确认号为请求来的序列号+1,新序列号为请求来的确认号),确保双方都接收到后TCP通道关闭。
tcp的差错控制(校验和、确认和超时重传)
TCP收到包后会检查校验和字段,如果遇到校验和有差错的报文,TCP 直接丢弃,等待重传。
确认:
接收方收到包后会发送一个ACK确认包告知收到并且在序列号中标记下一个希望接收到的序列号开头,TCP中采用“累计确认”、“延迟确认”、“不逐一确认”方式,只要收到一个帧即默认之前序列号的帧都正确收到,并且接收方不用对每一帧进行ACK回复,每隔一段时间才会合并发一个确认帧。
延迟确认(delayed ack)所做的事情,就是后者,稍稍延迟,然后合并 ACK,最后才回复给发送端。TCP 要求这个延迟的时延必须小于500ms,一般操作系统实现都不会超过200ms。
不过需要主要的是,有一些场景是不能延迟确认的,收到了就要马上回复:
- 接收到了大于一个 frame 的报文,且需要调整窗口大小
- TCP 处于 quickack 模式(通过tcp_in_quickack_mode设置)
- 发现了乱序包
超时重传:
当发送端发送一段字节的数据后,会把这个报文段保存在一个队列中,并启动一个计时器(RTO / 重传计时器 计时器的值是动态确认的,与RTT有关)。
-
快重传
在 TCP 传输的过程中,如果发生了丢包,即接收端发现数据段不是按序到达的时候,接收端的处理是重复发送之前的 ACK。
比如第 5 个包丢了,即使第 6、7 个包到达的接收端,接收端也一律返回第 4 个包的 ACK。
当发送端超时了仍然没有收到接收端的ack确认,那么发送端会把队列中的首部报文重新发送。或者当发送端收到 3 个重复的 超时重传ACK 时,它可以不等待计时器直接重传报文段,这种机制也称为快重传。 -
选择性重传
接收端在发送超时重传ACK时,会在报文的首部带上一个已经接收到的区间数据,因此发送端收到后就可以选择性重传而不是全部重传。
解释如下:
既然要重传,那么只重传第 5 个包还是第5、6、7 个包都重传呢?
当然第 6、7 个都已经到达了,TCP 的设计者也不傻,已经传过去干嘛还要传?干脆记录一下哪些包到了,哪些没到,针对性地重传。
在收到发送端的报文后,接收端回复一个 ACK 报文,那么在这个报文首部的可选项中,就可以加上SACK这个属性,通过left edge和right edge告知发送端已经收到了哪些区间的数据报。因此,即使第 5 个包丢包了,当收到第 6、7 个包之后,接收端依然会告诉发送端,这两个包到了。剩下第 5 个包没到,就重传这个包。这个过程也叫做选择性重传(SACK,Selective Acknowledgment),它解决的是如何重传的问题。
tcp的流量控制(滑动窗口、接收窗口、发送窗口)
对于发送端和接收端而言,TCP 需要把发送的数据放到发送缓存区, 将接收的数据放到接收缓存区。(缓冲等待队列)
接收缓存区中包含已接收但还未确认的数据和剩余空闲字节数,剩余的空间即为接收窗口。发送缓存区其实就是发送窗口,包含已发送但是未确认的数据和即将发送的数据。
流量控制要做的事情,就是在通过接收缓存区的大小,控制发送端的发送。如果对方的接收缓存区满了,就不能再继续发送了。
接收方每次收到数据包,可以在发送确定报文的时候,同时告诉发送方自己的缓存区还剩余多少是空闲的,我们也把缓存区的剩余大小称之为接收窗口大小,用变量win来表示接收窗口的大小。
发送方收到之后,便会调整自己的发送速率,也就是调整自己发送窗口的大小,当发送方收到接收窗口的大小为0时,发送方就会停止发送数据,防止出现大量丢包情况的发生。当发送方收到接受窗口 win = 0 时,这时发送方停止发送报文,并且同时开启一个定时器,每隔一段时间就发个测试报文去询问接收方,打听是否可以继续发送数据了,如果可以,接收方就告诉他此时接受窗口的大小;如果接受窗口大小还是为0,则发送方再次刷新启动定时器
具体例子:
握手建立连接后,双方初始化各自的窗口大小,服务器向客户端发送的报文段中说明接收窗口大小(字节为单位),而发送窗口不能大于接收窗口。
发送窗口和接受窗口相等吗?
答:接收方在发送确认报文的时候,会告诉发送发自己的接收窗口大小,而发送方的发送窗口会据此来设置自己的发送窗口,但这并不意味着他们就会相等。首先接收方把确认报文发出去的那一刻,就已经在一边处理堆在自己缓存区的数据了,所以一般情况下接收窗口 >= 发送窗口。
tcp的拥塞控制(慢启动、拥塞避免、快重传、快恢复)
对于拥塞控制来说,TCP 每条连接都需要维护两个核心状态:
- 拥塞窗口(Congestion Window,cwnd)
- 慢启动阈值(Slow Start Threshold,ssthresh)
涉及到的算法有: - 慢启动
- 拥塞避免
- 快速重传和快速恢复
拥塞窗口(Congestion Window,cwnd)
发送方维持一个叫做拥塞窗口 cwnd (congestion window)的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。
发送方让自己的发送窗口等于拥塞窗口。如再考虑到接收方的接收能力,则发送窗口还可能小于拥塞窗口。
拥塞窗口(cwnd)和接收窗口(rwnd)用于限制发送窗口的大小。发送窗口大小 = min(rwnd, cwnd)
发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去。但只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数。
慢启动和拥塞避免
首先,三次握手,双方宣告自己的接收窗口大小,发送方初始化拥塞窗口(cwnd)大小。
在开始传输的一段时间,发送端每收到一个 ACK(每经过一个 RTT),拥塞窗口大小翻倍。如果说初始窗口为 10,那么第一轮 10 个报文传完且发送端收到 ACK 后,cwnd 变为 20,第二轮变为 40,第三轮变为 80,依次类推。
指数型增长到达慢启动阈值ssthresh后,就会改为线性增长,每收到一个确认帧后窗口大小+1。
指数型增长阶段被称为慢启动,而线性增长阶段被称为拥塞避免。
当网络开始出现拥塞时
丢包后,有两种重传方式,对应不同的网络情况,也就对应着两种拥塞发生时的控制算法:
- 接收方超时计时器超时
TCP认为这种情况比较糟糕,调整力度比较大:
- 把慢启动阈值设置为出现拥塞时的发送方窗口值的一半(但不能小于2)。
- 然后把拥塞窗口 cwnd 重新设置为 1,执行慢开始算法。
- 快速重传(收到三次重复ACK确认)
TCP认为现在的网络拥塞情况没有那么严重。
因此现在不执行慢开始算法,即拥塞窗口 cwnd 现在不设置为 1,而是设置为慢开始门限 ssthresh 减半后的数值,然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。
快重传和快恢复
都是网络拥塞时的处理。
快重传在TCP的超时处理中已经讲过了,就是指如果发送方连续收到三次重复发送的ACK确认报文,可以不理会超时计时器的信息,立即重传对方未接收到的报文段。
快恢复当发送端收到连续三个重复的确认时,就把慢开始门限 ssthresh 减半。但接下去不执行慢开始算法。 由于发送方现在认为网络的拥塞情况没有那么严重,因此现在不执行慢开始算法,即拥塞窗口 cwnd 现在不设置为 1,而是设置为慢开始门限 ssthresh 减半后的数值,然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。