在说这个问题之前,我们得先知道,TCP 报文段的首部格式
-
序号 :用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。
-
确认号 :期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段,序号为 501,携带的数据长度为 200 字节,因此 B 期望下一个报文段的序号为 701,B 发送给 A 的确认报文段中确认号就为 701。
-
数据偏移 :指的是数据部分距离报文段起始处的偏移量,实际上指的是首部的长度。
-
确认 ACK :当 ACK=1 时确认号字段有效,否则无效。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 置 1。
-
同步 SYN :在连接建立时用来同步序号。当 SYN=1,ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接,则响应报文中 SYN=1,ACK=1。
-
终止 FIN :用来释放一个连接,当 FIN=1 时,表示此报文段的发送方的数据已发送完毕,并要求释放连接。
-
窗口 :窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。
-
客户 clientA 发送 SYN 同步序号和随机产生一个值 seq=J(也就是序号=J)给服务器 B,此时处于 SYN_SEND 状态
-
服务器 B 被动打开,处于 Listen 状态,服务器 B 接收到客户端 A 发送的请求后,如果同意请求连接,发送 SYN 同步序号 和 ACK=1 使得确认号字段有效,并且随机产生一个值 seq=K (序号为 K),然后 ack 确认号 J+1,也就是说,期望下一个数据包的序号为 J+1,为此时进入到 SYN_RCVD 状态
-
客户端 A 收到服务器 B 的确认之后,检查 ack 是否为 J+1,ACK 是否为 1,如果正确则将标志位 ACK 置为 1,ack=K+1 (我希望下一个从 B 发给我的数据报的序号为 K+1),如果正确则将标志位 ACK 置为 1,ack=K+1,并将该数据包发送给服务器 B,此时进入 established 状态,服务器 B 检查 ack 是否为 K+1 ,ACK 是否为 1,如果正确则连接建立成功,此时进入 established 状态
为什么不是二次握手 ?
为了解决已失效的请求报文又传到 B,因而产生错误。
比如 A 发送了一个请求给 B,希望建立连接。但是由于此时此刻网络节点长或者拥塞滞留,这本来是一个失效的报文请求,但是因为 A 有一个超时时间,这时候会被认为超时重传。如果是两次握手请求,那么 B 这时候收到这个 “ 延迟 ”的报文请求 A,会认为 A 又发送请求,于是 B 发送确认,这时候又建立起了连接。
在“TCP 建立连接为什么是三次握手?”的问题,在众多回复中,有一条回复写道:“这个问题的本质是, 信道不可靠, 但是通信双发需要就某个问题达成一致. 而要解决这个问题, 无论你在消息中包含什么信息, 三次通信是理论上的最小值. 所以三次握手不是 TCP 本身的要求, 而是为了满足"在不可靠信道上可靠地传输信息"这一需求所导致的. 请注意这里的本质需求,信道不可靠, 数据传输要可靠. 三次达到了, 那后面你想接着握手也好, 发数据也好, 跟进行可靠信息传输的需求就没关系了. 因此,如果信道是可靠的, 即无论什么时候发出消息, 对方一定能收到, 或者你不关心是否要保证对方收到你的消息, 那就能像 UDP 那样直接发送消息就可以了.”。这可视为对“三次握手”目的的另一种解答思路。
-
客户端 A 发送一个 FIN 告知服务器 B,要结束连接,此时进入 FIN_WAIT_1 状态
-
服务器 B 收到 FIN 后,发送一个 ACK 给客户端 A,确认序号为收到序号+1(与 SYN 相同,一个 FIN 占用一个序号),服务器 B 进入 CLOSE_WAIT,处于半关闭状态,也就是 A 不向 B 发数据,但 B 要向 A 传数据,客户端处于 FIN_WAIT_2 状态
-
服务器 B 发送一个 FIN,这时 B 没有东西要向 A 传了,所以发送 FIN 告知 A 可以关闭了,然后进入 LAST_ACK 状态
-
客户端 A 收到 FIN 后,客户端进入 TIME_WAIT 状态,需要等待 2MSL 时间(报文最大生存时间),接着发送一个 ACK 给服务器 B,确认序号为收到序号+1,注意,这里还未释放掉,必须经过 Time-wait(等待时间),服务器 B 才进入 CLOSED 状态,完成四次挥手。
为什么要四次挥手?
客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。
TIME_WAIT
客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由:
-
确保最后一个确认报文能够到达。如果 B 没收到 A 发送来的确认报文,那么就会重新发送连接释放请求报文,A 等待一段时间就是为了处理这种情况的发生。
-
等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。