TCP 连接分为三个部分:启动、数据传输和退出。
连接建立
建立一条 TCP 连接需要完成以下三步:
- 客户端将一个特殊的 TCP 报文封装在一个 IP 数据报中发送给服务器端,其中不包含任何应用层数据但 SYN 置为 1 以表明想要建立一条连接。同时会发送客户端的初始序号 client_isn 以及选项(如果有的话)。
- 服务器收到后,如果同意建立连接的话会返回一个 SYNACK 报文段以表明自己已准备好连接,其中 SYN 同样置为 1,ACK 为 client_isn + 1 以确认之前客户端发送的报文,最后将自己的初始序号 server_isn 放入报文。
- 客户端收到了 SYNACK 后,会返回另一个报文段给服务器端,此时 SYN 置为 0,ACK 为 server_isn + 1,表明自己准备好连接。
这个过程中 isn 的交换和 ack 值的确定与 TCP 正常传输相同,表明建立连接的核心在于 SYN 的值,可以认为是希望对方能对此做好准备,所以这个过程可以认为是如下对话:
- 客户端:我想建立连接,希望你准备好(SYN),我的序号为 client_isn
- 服务端:我收到了你的 client_isn 请求(ACK),我同意建立此连接,我已准备好,希望你也准备好(SYN),我的序号为 server_isn
- 客户端:我收到了你的 server_isn 回复(ACK),我已准备完毕,我的序号 client_isn + 1
注意到第三步因为对方已准备好,此时并没有想要对方做好准备的意思,因此 SYN 置为 0,避免了与第一步的含义发生冲突,在之后发送的所有数据报中 SYN 始终都被置为 0。
这一过程常被称为 三次握手,完成后双方就可相互发送数据。之所以需要这一过程是为了让双方都为该连接准备好资源,如内存和变量初始化,同时交换了双方的初始序号(Initial Sequence Number, ISN)。
对于第三个过程看似可以通过发送数据的方式替代,但实则不然,其目的是为了防止服务器因为失效的请求建立无效的连接。试想一个延迟较高的网络,客户端第一次请求由于延迟超时,再次发送了请求,这样服务器会收到两次请求,都会建立连接,但客户端会忽略第一次连接的确认报文而无返回。这时如果有第三个过程,服务器就可以知道第一次连接失效可以将其关闭。
连接关闭
对于连接的双方都可以提出关闭连接的请求,当双方都关闭时此时完成关闭,可以分为如下四部:
- 主动关闭者 A 发送一个特殊报文,将 FIN 置为 1,同时有 Seq 表明对方希望看到的自己当前序列号 K 和 ACK 确认对方最近发来的一个数据 L。
- 被动关闭者 B 收到后,将 K + 1 作为 ACK 值返回表明自己已收到对方的关闭请求。此时处于半关闭状态,A 不再能发送数据但 B 仍能发送给 A 。
- 当 B 数据发送完毕后,为了关闭自己的连接便转变为主动关闭者,同样发送一个 FIN 报文,序列号为 L。
- A 收到后再返回个确认报文,进入 TIME_WAIT 状态后,等待 2MSL 后释放连接。B 在收到 A 的 ACK 后释放连接。
这个过程也被称为 四次挥手,FIN 置 1 的含义为自己已完成了向对方的数据传输,又因为 TCP 是双向的,所以只有当双方都发送了 FIN 报文,即双方都完成了数据传输,此时才可释放连接。
相比建立连接多了一个步骤,在于被动关闭者对于对方的关闭确认和自己的关闭请求分为了两步,原因在于他可能还有数据未发送完,这时已只能单向传输数据所以处于半关闭状态。
值得注意的是,在最后一个环节时返回 ACK 时,发送的 ACK 不占用序列号,但这个 ACK 仍有丢失的风险,解决方法由 TIME_WAIT 状态处理,在下面有详细说明。
TCP 状态转换
之前介绍了 TCP 连接和关闭的过程,本质上其实是在 TCP 的各个状态间进行转换,发送的 SYN、FIN 等就是状态转换的标志。
上图中粗黑线代表典型的客户端状态转移,蓝线代表典型的服务器状态转移。从中可以找到连接建立和关闭的状态转换。
TIME_WAIT
TIME_WAIT 状态又称 2MSL 等待状态,处于在接收到 FIN 后发送 ACK 之后,此时需要等待两倍于最大段生命(Maximum Segment Lifetime, MSL)的时间。
由于发送 FIN 的一方在没有 ACK 的时候会不断发送 FIN 直到得到回复,TIME_WAIT 这一段时间就是为了防止对方没有收到自己发送的 ACK,如果再次收到 FIN 就说明对方并未收到 ACK,重发即可。
同时打开和关闭
在上图中有两处表明了同时打开和关闭的地方,虽然这种情况发送几率较小但 TCP 仍对此作了处理以保证只建立一条连接以及能正常关闭。
当双方同时建立连接时,相比正常建立连接多了一个报文,此时双方都既是服务器又是客户端,双方都得回复一个 SYN + ACK。同时关闭与正常的关闭类似,只是传递的报文段是交叉的。
连接重置
一般而言当传输发生错误时,会使用一个重置报文(RST 设为 1)终止连接,下面是会用到重置报文的一些场景。
- 连接端口不存在:端口不存在时连接被拒绝,TCP 协议用重置报文完成此工作。
- 终止连接:用 FIN 可以安全关闭连接,但是有时不想再处理已发出数据的情况,例如错误的开始发送一个大型文件,可以用 RST 快速结束连接。
- 半开连接的终止:当某一方意外,在没有告知对方的情况下关闭了连接,例如停电,这时当对方再次发送数据就可以返回 RST 使对方关闭连接。
重置报文不会有应答,发送之后自己就会立即关闭连接。如果重置报文发生丢包,那么对方再次发送数据就会发送第二个情况再次返回 RST。如果重置报文始终丢包,会触发对方的重传机制,直到重传超时关闭连接。