参考资料 :小林Coding、阿秀、代码随想录
一、什么是TCP协议
TCP是传输控制协议Transmission Control Protocol
TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。
- 面向连接的:每条TCP连接杜只能有两个端点,每一条TCP连接只能是点对点的(一对一)
- 可靠的:可靠交付
- 基于字节流的:
- TCP中的“流”的概念是流入或流出进程的字节序列
- 基于字节流:虽然应用程序和TCP的交互是一次一个数据块,但TCP把应用程序交下来的数据仅仅看成是一连串的无结构的字节流
- TCP不保证接收方应用程序收到的数据块和发送方应用程序所发出的数据块具有对应的大小关系,但是接收方应用程序收到的字节流必须和发送方应用程序发出的字节流完全一样。
二、TCP的连接建立
1. 三报文握手
- TCP 建立连接的过程叫做握手。
- 握手需要在客户和服务器之间交换三个 TCP 报文段。称之为三报文握手。
- 三报文握手主要作用是为了确认通信双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。
- 采用三报文握手可以防止已失效的连接请求报文段突然又传送到了,因而产生错误。
1) 初始状态:
客户端处于 closed(关闭) 状态,服务器处于 listen(监听) 状态。
2) 第一次握手:
客户端给服务器发⼀个 SYN 报⽂,指明客户端的初始化序列号 ISN(c)。此时客户端处于SYN_SEND 状态。
该报文首部的同步位 SYN = 1,初始序号 seq = x,SYN = 1。
该报文段不能携带数据,但要消耗掉⼀个序号。
3) 第二次握手:
服务器收到客户端的 SYN 报文之后,以自己的 SYN 报⽂作为应答,并且指定自己的初始化序列号 ISN(s)。同时把客户端的 ISN + 1 作为 ACK 的值,表示已经收到了客户端的 SYN,此时服务器处于 SYN_RCVD 的状态
在确认报文段中 SYN = 1,ACK = 1,确认号 ack = x + 1,初始序号 seq = y。
4) 第三次握手:
客户端收到 SYN 报文之后,会发送⼀个 ACK 报⽂,把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报⽂,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报⽂ 之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接。
确认报文段 ACK = 1,确认号 ack = y + 1,序号 seq = x+1(初始为 seq = x,第⼆个报文段所以要+1)
该ACK报文段可以携带数据,不携带数据则不消耗序号。
2. 为什么需要三次握手?
1) 阻止重复历史连接的初始化
网络阻塞时,客户端向服务器发送两次SYN请求报文,旧的SYN报文先到达服务器,服务器回复一个ACK+SYN报文,客户端根据自身上下文判断这是一个历史连接(序列号过期或者超时),那么客户端就会发送RST报文给服务端,终止这次连接,服务器收到RST报文后,释放连接。这样新的SYN报文到达后,客户端与服务器就可以正常进行三次握手。
若采用两次握手,服务器在收到SYN报文后,进入ESTABLISHED状态,服务器并不知道这是历史连接,直接与客户端建立并向客户端发送数据,造成资源浪费,但是客户端会判定这次连接是历史连接,忽略客户端确认消息,也不发送数据,服务端一直等待客户端发送数据。
2) 同步双方初始序列号
TCP协议的通信双方,都必须维护一个序列号,序列号是可靠传输的一个关键因素。
- 接收端 -> 去除重复元素 ,并且按照序列号顺序接收数据
- 发送端 -> 确认发送的数据包哪些已经被收到
三次握手确认初始化序列过程:
- 第一次握手:客户端发送携带客户端初始化序列号的SYN报文;
- 第二次握手:服务器发送携带服务器初始化序列号以及客户端初始化序列号+1的ACK + SYN应答报文,表示收到客户端SYN报文;
- 第三次握手:客户端发送携带服务器初始化序列号+1的ACK应答报文。
两次握手只能保证服务器成功接收了客户端的初始化序列号,但无法确认服务器的初始化序列号是否被成功接收。
3. 半连接队列
半连接队列(SYN队列)⽤于存放已经发送了 SYN(同步)包,但还未完成三次握⼿的连接。服务器第⼀次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双⽅还没有完全建⽴其连接,服务器会把此种状态下请求连接放在⼀个队列⾥,我们把这种队列称之为半连接队列。
全连接队列(Accept队列)⽤于存放已经完成三次握⼿,处于完全建⽴连接状态的连接。
4. 三次握手可以携带数据吗?
三次握手中,第一次和第二次连接不可以携带数据,第三次连接可以携带数据。主要原因是如果第一次握手如果携带数据,会让服务器更加容易被攻击,但是第一次握手会消耗一个序列号,对于第三次握手,客户端已经处于ESTABLISHED状态,确认服务器接收、发送能力没有问题,可以发送数据。
5. SYN洪泛攻击
SYN攻击指Client在短时间内伪造大量不存在的IP地址,并向Server不断发送SYN包,Server则回复确认包,等待Client确认。由于源地址不存在,因此Server需要不断重新发送直到超时,这些伪造的SYN包会长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而导致网络拥塞甚至系统瘫痪。
检测SYN攻击:在服务器上发现大量半连接状态,特别其源地址IP是随机的,基本确认为SYN攻击。Linux系统下可使用 netstats 命令检测:
netstat -n -p TCP | grep SYN_RECV
防御SYN攻击:
-
缩短超时(SYN Timeout)时间
-
增加最大半连接数
-
过滤网关防护
-
SYN Cookies技术
三、TCP的连接释放
1. 四报文挥手
- 数据传输结束后,通信的双方都可释放连接。
- TCP 连接释放过程是四报文握手。
- TCP的半关闭(half-close):TCP提供了连接的⼀端在结束它的发送后还能接收来⾃另⼀端数据的能力。
- 客户端和服务器均可以主动发起挥手。
1) 初始状态:
双方都处于 ESTABLISHED 状态 (假设客户端发起挥手)
2) 第一次挥手:
客户端发送⼀个 FIN 报⽂,报⽂中会指定⼀个序列号(FIN=1,序号seq=u)。此时客户端停止再发送数据,主动关闭TCP连接,进⼊FIN_WAIT1状态,等待服务端的确认。
3) 第二次挥手:
服务端收到以后,向客户端发送ACK应答报文,且把客户端的 序列号值+1 作为 ACK 报文的序列号值(ACK=1,确认号ack=u+1,序号seq=v),表明已经收到客户端的报文,服务器处于 CLOSE_WAIT 状态,此时TCP处于半关闭状态,客户端到服务端的连接释放。
客户端收到服务端的确认后,进⼊FIN_WAIT2状态,等待服务端发出的连接释放报文段。
4) 第三次挥手:
如果服务器也想断开连接了,和客户端的第⼀次挥手⼀样,发出 FIN 连接释放报文(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),且指定⼀个序列号。此时服务器处于LAST_ACK 的状态,等待客户端的确认。
5) 第四次挥手:
客户端收到 FIN 之后,发送⼀个 ACK 报文作为应答,且把服务器的序列号值 +1 作为自己 ACK 报文的确认号值(ACK=1,seq=u+1,ack=w+1),客户端处于 TIME_WAIT 状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,客户端才进入CLOSED状态。
服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。
2. 为什么需要四次挥手?
关闭连接时,客户端发送FIN报⽂,表示其不再发送数据,但还可以接收数据。服务端收到FIN报文,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是⽤来同步的。但是关闭连接时,服务端可能还有数据需要处理和发送,所以先回⼀个ACK应答报文,等到其不再发送数据时,才发送 FIN报文给客户端表示同意关闭连接。
服务端通常需要等待完成数据的发送和处理,所以服务端的ACK和FIN⼀般都会分开发送,因此需要四次挥手。
3. 为什么TIME_WAIT状态时间为2MSL?
1)MSL是Maximum Segment Lifetime,可译为“最长报文段寿命”,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
2)网络中可能存在发送方的数据包,当这些发送方的数据包被接收方处理后⼜会向对方发送响应,所以⼀来⼀回需要等待 2 倍的时间。
3)1 个 MSL 确保四次挥手中主动关闭方最后的 ACK 报文最终能达到对端;1 个 MSL 确保对端没有收到 ACK 重传的 FIN 报文可以到达。
2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报⽂,那么 2MSL 时间将重新计时。
4. 为什么需要 TIME_WAIT 状态(2MSL状态)?
1) 防止历史连接中的数据,被后面相同四元组的连接错误的接收
如果网络出现拥塞或延迟,数据包可能会在网络中滞留⼀段时间,甚⾄超过了原始连接关闭的时间。如果没有 TIME_WAIT 状态,客户端直接进入到CLOSE状态,这些滞留的数据包可能会被传递给新连接,导致新连接的数据被旧连接的数据干扰。经过 2MSL 这个时间,足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络都自然消失,再出现的数据包⼀定都是新建立连接所产生的。
2) 保证被动关闭连接的⼀方能被正确的关闭,即保证最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。
如果最后的⼀次ACK报文丢失(第四次挥手),客户端没有 TIME_WAIT 状态,直接进⼊ClOSE,服务端⼀直在等待 ACK状态,⼀直没有等到,就会重发FIN报文,客户端已经进入到关闭状态,在收到服务端重传的 FIN 报⽂后, 就会回 RST 报文,服务端收到这个 RST 并将其解释为⼀个错误,。为了防止这种情况出现,客户端必须等待足够长的时间,确保服务端能够收到 ACK,如果服务端没有收到 ACK,那么就会触发 TCP 重传机制,服务端会重新发送⼀个 FIN,这样⼀去⼀来刚好两个 MSL 的时间。