一. 什么是三次握手?
-
最初主机 A 和主机 B 都处于 CLOSED(关闭) 状态。A 主动打开连接,B 被动打开连接,然后 B 进入 LISTEN(监听)状态,等待客户的连接请求。
-
第一次握手:A 向 B 发出连接请求,请求报文段的同步位 SYN = 1,初始序号 seq = x 。这个报文段不能携带数据,但会消耗掉一个序号。然后 A 进入 SYN_SENT(同步已发送)状态。
-
第二次握手:B收到请求报文段后,如果同意连接,就向 A 发出确认,确认报文段的 SYN 位和 ACK 位都为 1 ,确认号 ack = x + 1 ,初始序号 seq = y 。这个报文段也不能携带数据,但会消耗掉一个序号。这时 B 进入 SYN_RCVD(同步收到)状态。
-
第三次握手:A 收到 B 的确认报文段后,还要向 B 发出确认,确认报文段的 ACK 位为 1,确认号 ack = y + 1 ,序号 seq = x + 1 。这个报文段可以携带数据,因为对于客户端 A 来说,已经处于 eatablished 状态建立起了连接,如果不携带数据则不消耗序号。这时TCP连接已经建立,A 进入 ESTABLISHED(已建立连接)状态。B 收到 A 的确认后,也进入已建立连接状态。
额外的点:
- ACK 是用来应答的;syn 是用来同步的。
- 序号 seq 如果是固定的,攻击者很容易猜到后续的确认号,因此 seq 是动态生成的。
- 第一次和第二次握手不能携带数据;而第三次握手可以携带数据,因为对于客户端 A 来说,已经处于 eatablished 状态建立起了连接。
- 如果第三次握手的ACK丢失了,导致服务端没有收到,那么服务端会重新发送 SYN + ACK 包给客户端,以便客户端重新发送 ACK 应答包。
二. TCP协议为什么要三次握手 ?两次不行吗?四次不行吗?
两次握手不行,因为:三次握手是为了防止已经失效的连接请求报文段突然又传到了服务端,从而产生错误。 比如:A 发出的第一个连接请求报文段在网路结点长时间滞留了,导致延误到连接释放之后的某个时间才到达 B。 本来这是一个早已失效的报文段,但是 B 会误以为这是 A 又发出一次新的连接请求,于是就向 A 发出确认报文段,同意建立连接。如果不采用第三次握手,那么只要 B 发出确认,B 就认为新的连接已经建立了。但由于 A 并没有发出建立连接的请求,所以不会理睬 B 的确认,也就不会向 B 发送数据,而 B 以为新的连接已经建立,就会一直等待 A 发来数据,这样就白白浪费了 B 的资源。如果采用了三次握手,B 由于收不到确认,就会知道 A 并没有要求建立连接,也就不会又打开连接了。
四次握手不行,因为在经过三次握手后,客户端和服务端已经可以确认建立连接了,所以没必要再增加握手次数。
三. 什么是四次挥手?(四次挥手的流程)
- 开始主机 A 和主机 B 都处于已建立连接状态。首先 A 向 B 发出连接释放报文段,并停止发送数据,主动关闭 TCP 连接。报文段首部的终止控制位 FIN 置为 1,序号 seq = u,这个序号等于前面已经传送过的数据的最后一个字节的序号加 1 。这时 A 进入 FIN-WAIT-1(终止等待1)状态,等待 B 的确认。(这个报文段会消耗掉一个序号)
- B 收到连接释放报文段后立即向 A 发出确认,确认号 ack = u + 1 ,序号 seq = v,这个序号等于 B 前面已经传送过的数据的最后一个字节的序号加 1 。然后 B 进入 CLOSE-WAIT(关闭等待)状态。此时 TCP 连接处于半关闭状态,也就是 A 已经没有数据要发送了,但 B 若发送数据,A 仍要接收,因为 B 到 A 这个方向的连接并未关闭。A 收到 B 的确认后,就进入 FIN-WAIT-2(终止等待2)状态,等待 B 发送连接释放报文段。
- 若 B 已经没有要向 A 发送的数据了,就通知 TCP 释放连接,向 A 发出连接释放报文段,报文段的终止控制位 FIN = 1 ,确认号 ack = u + 1 ,序号 seq = w 。B 进入 LAST-ACK(最后确认)状态,等待 A 的确认。
- A 收到 B 的连接释放报文段后,发出确认释放报文段,报文段的确认位 ACK 为 1,确认号 ack = w + 1 ,序号 seq = u + 1(因为前面 FIN 报文段消耗了一个序号)。然后进入 TIME-WAIT(时间等待)状态。此时 TCP 连接还没释放掉,必须经过时间等待计时器设置的 2MSL 时间后,A 才进入关闭状态,MSL 指的是报文最长存活时间。B 一收到 A 的确认,就进入关闭状态。所以在释放连接时, B 结束 TCP 连接的时间要早于 A。
(A 之所以等待 2MSL 时间,有两个原因(time_wait的作用):一是为了保证 A 发送的最后一个 ACK 报文段能够到达 B,如果这个报文段丢失,导致 B 未收到 A 发来的确认报文,那么 B 会超时重传这个 FIN + ACK 报文段,而 A 就能在 2MSL 时间内收到这个重传的报文段,接着 A 重传一次确认,并重新启动 2MSL 计时器。最后使 A 和 B 都正常进入到关闭状态。二是等待 2MSL 时间,可以使本次连接持续时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现旧的连接请求报文段)
四. 为什么要有四次挥手,三次挥手为什么不行?
- 关闭连接时,服务端收到 FIN 终止报文后,并不能立即关闭连接,而是先回复一个 ACK 确认报文给客户端,告诉客户端“你发的 FIN 终止报文我已收到”,然后服务端进入(closed-wait) 关闭等待状态,这么做的目的是为了让服务端将数据都发送完毕,发送完毕后服务端才再发送 FIN 终止报文来释放连接。
注意:四次挥手为什么第二次跟第三次不能合并, 第二次和第三次之间的等待是什么?
- 当服务器执行第二次挥手之后, 此时客户端不会再向服务端发送数据, 但是服务端可能还正在给客户端发送数据(可能是客户端上一次请求的资源还没有发送完毕), 所以服务端会等把数据传输完毕之后再发送释放连接报文(也就是第三次挥手)。
五. TIME-WAIT 过多的原因?影响?解决办法?
原因: 高并发可以让服务器在短时间内占用大量端口,端口范围是0~65535,客户端每断开一个连接,该连接就会进入timewait状态,默认60s超时回收。在这种高并发场景下容易造成TIME-WAIT过多,没有端口可用,导致连接失败。
解决办法:
- 编辑内核文件,允许 TIME-WAIT sockets 重新用于新的TCP连接: 编辑内核文件 /etc/sysctl.conf ,里面的 reuse 默认为0,将它改为1,表示开启重用,开启后可以允许 TIME-WAIT sockets 重新用于新的TCP连接。
- 开启 TIME-WAIT sockets 的快速回收: recycle 默认为0,将它改为1。
- 修改系統默认的 TIMEOUT 时间,默认为60秒,可以修改为30秒。