1. 建立连接
TCP 建立连接的过程需要在客户端和服务端之间交换 3 个 TCP 报文段,故而也称为 “三次握手”。
假设 A 运行 TCP 客户端程序,B 运行 TCP 服务端程序,则三次握手过程如下所示:
- 开始时,两者各自创建一个 TCP 套接字,此时两者都处于 CLOSED 状态;
- 然后,服务端 B 将套接字标记为被动的,进入 LISTEN 状态,监听客户端连接请求;
- A 向 B 发送一个 SYN 报文段,其中的同步位 SYN=1,同时选择一个初始序号 seq=x;SYN 报文段不能携带数据,但要消耗一个序号,在发送完 SYN 报文段之后,进入 SYN_SENT 状态;
- B 收到 A 的连接请求报文后进入 SYN_RCVD 状态,然后向 A 发送确认:确认报文段的 SYN=1、ACK=1,确认号为 ack=x+1,同时为自己选择一个初始序号 seq=y,此报文段也不能携带数据,但要消耗一个序号;
- A 收到 B 的确认后,还要向 B 返回确认,然后进入 ESTABLISHED 状态:确认报文的 ACK=1,确认序号为 ack=y+1,而自己的序号为 seq=x+1,此报文段可以携带数据,如果不携带数据则不消耗序号(下一个报文段的序号仍为 seq=x+1);
- B 收到 A 的确认后也进入 ESTABLISHED 状态。
为什么 A 最后还要发送一次确认? 主要是为了防止已失效的连接请求报文段突然又传送到了 B:如果 A 发出的第一个连接请求报文段因为网络延迟没有及时到达 B;此时 A 发出第二个连接请求报文段,且成功建立了连接,数据传输完毕后,释放了连接;一段时间后,第一个连接请求报文段终于到达了 B(B 以为是一个新的连接请求),如果 B 不需要 A 的确认就建立了连接,而 A 又不理会 B 的确认,则 B 就白白浪费了资源。
2. 释放连接
假设 A 主动关闭连接,则连接的释放过程如下:
- A 关闭套接字,向 B 发送连接释放报文段:报文段的 FIN=1,序号 seq=u;然后进入 FIN-WAIT-1 状态,等待 B 的确认;FIN 报文段不能携带数据,但要消耗一个序号;
- B 收到连接释放报文段后立即发出确认:确认报文段的 ACK=1,确认号为 ack=u+1,序号为 seq=v;然后进入 CLOSE-WAIT 状态(等待己方关闭连接);此时的 TCP 连接处于半关闭状态,即 A 已经没有数据要发送了,但如果 B 发送数据,A 仍要接收,也就是说 B 到 A 这个方向的连接仍未关闭;
- A 收到 B 的确认后进入 FIN-WAIT-2 状态,等待 B 发送连接释放报文段;
- B 发送完数据之后也关闭连接,向 A 发送连接释放报文段:报文段的 FIN=1,ACK=1,序号为 seq=w,确认号仍为 ack=u+1;然后进入 LAST-ACK 状态,等待 A 的确认(最后一个确认);
- A 收到 B 的连接释放报文段后,向 B 发出确认:报文段的 ACK=1,序号为 seq=u+1,确认号为 ack=w+1;然后进入 TIME-WAIT 状态,此时连接仍未释放;
- B 收到 A 的确认后释放连接,进入 CLOSED 状态;
- A 在 TIME-WAIT 状态中等待 2MSL(Maximum Segment Lifetime)的时间之后才进入到 CLOSED 状态,即 B 先于 A 结束 TCP 连接。
为什么 A 需要在 TIME-WAIT 状态中等待 2MSL 的时间? 原因有如下两点:
(1)为了保证 A 发送的最后一个 ACK 报文段能够到达 B。如果此 ACK 报文段丢失,B 会因为没收到确认而超时重传 FIN+ACK 报文段,而 A 就能够在这 2MSL 时间内收到重传的 FIN+ACK 报文段,然后 A 重传一次确认,并重新启动 2MSL 计时器。
(2)A 在发送完最后一个 ACK 报文段后,再经过 2MSL 时间,就可以使本连接期间所产生的所有报文段都从网络中消失,这样就可保证新连接中不会出现旧连接的请求报文段。