目录

TCP连接管理

名词解释

名词 解释
SYN 同步序号,用于建立连接过程,在连接请求中,SYN=1 和 ACK=0 表示该数据段没有使用捎带的确认域,而连接应答捎带一个确认,即 SYN=1 和 ACK=1
FIN finish 标志,用于释放连接,为 1 时表示发送方已经没有数据发送了,即关闭本方数据流
ACK 确认序号标志,为 1 时表示确认号有效,为 0 表示报文中不含确认信息,忽略确认号字段
PSH push 标志,为 1 表示是带有 push 标志的数据,指示接收方在接收到该报文段以后,应尽快将这个报文段交给应用程序,而不是在缓冲区排队
RST 重置连接标志,用于重置由于主机崩溃或其他原因而出现错误的连接。或者用于拒绝非法的报文段和拒绝连接请求
序列号 seq 占 4 个字节,用来标记数据段的顺序,TCP 把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号 seq 就是这个报文段中的第一个字节的数据编号
确认号 ack 占 4 个字节,期待收到对方下一个报文段的第一个数据字节的序号;序列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号 +1 (ACK会占一个序号)即为确认号
Info
ACKSYNFIN 这些大写的单词表示标志位,其值要么是 1,要么是 0;ackseq 小写的单词表示序号。

ACK 是可能与 SYNFIN 等同时使用的。比如 SYNACK可能同时为 1,它表示的就是建立连接之后的响应搜索 如果只是单个的一个SYN,它表示的只是建立连接。

SYNFIN是不会同时为 1 的,因为前者表示的是建立连接,而后者表示的是断开连接。

RST一般是在FIN之后才会出现为 1 的情况,表示的是连接重置。

一般,当出现FIN包或RST包时,便认为客户端与服务器端断开了连接;而当出现SYNSYN+ACK包时,我们认为客户端与服务器建立了一个连接。

PSH为 1 的情况,一般只出现在DATA内容不为 0 的包中,也就是说PSH为1表示的是有真正的 TCP 数据包内容被传递。

三次握手

https://cdn.xiaobinqt.cn/xiaobinqt.io/20220326/43c6821afd6d4a9fadcceb9e98c228b9.png?imageView2/0/q/75|watermark/2/text/eGlhb2JpbnF0/font/dmlqYXlh/fontsize/1000/fill/IzVDNUI1Qg==/dissolve/52/gravity/SouthEast/dx/15/dy/15
三次握手

第一次握手

客户端

主动打开(active open),向服务端发送 SYN 报文段SYN=1, SN=client_isn, OPT=client_mss,请求建立连接。

client_isn 是客户端初始序号,动态生成,用于实现可靠传输,client_sn-client_isn 等于客户端已发送字节数。

SYN 报文段虽然不能携带数据,但是会消耗一个序号(相当于发送了1个字节的有效数据),下次客户端再向服务端发送的报文段中 SN=client_isn+1。 除了 SYN 报文段和 ACK-SYN 报文段,其他所有后续报文段的序号 SN 值都等于上次接收的 ACK 报文段中的确认号 AN 值。

client_mss 是客户端最大报文段长度,在 TCP 首部的选项和填充部分,会在客户端与服务端的 MSS 中选择一个较小值使用。

客户端变为 SYN_SENT 状态,然后等待服务端 ACK 报文段。

第二次握手

服务端

接收来自客户端的 SYN 报文段,得知客户端发送能力正常。

被动打开passive open,向客户端发送 SYN-ACK 报文段ACK=1, AN=client_isn+1, SYN=1, SN=server_isn, OPT=server_mss ,应答来自客户端的建立连接请求并向客户端发起建立连接请求。

SN=server_isn 是服务端初始序号,ACK-SYN 报文段虽然不能携带数据,但是会消耗一个序号(相当于发送了1个字节的有效数据),下次服务端再向客户端发送的报文中 SN=server_isn+1

OPT=server_mss 是服务端最大报文段长度。

AN=client_isn+1 是确认号,表明服务端接下来要开始接收来自客户端的第 client_isn+1 个字节的有效数据。

服务端变为 SYN_RCVD 状态,并等待客户端 ACK 报文段。

第三次握手

客户端

接收来自服务端的 SYN-ACK 报文段,得知服务端发送能力和接收能力都正常。

向客户端发送 ACK 报文段ACK=1, AN=server_isn+1, SN=client_isn+1, MESSAGE=message,应答来自服务端的建立连接请求。

SN=client_isn+1 是序号,表明当前报文段发送的有效数据首字节是从请求建立连接到现在为止客户端向服务端发送的第 (client_isn+1)-clien_isn+1=2 个字节的有效数据。

有效数据:一般有效数据指的是应用层的报文数据,不过 SYN 报文段、 ACK-SYN 报文段和 FIN 报文段虽然没有携带报文数据,但认为发送了1个字节的有效数据。

AN=server_isn+1 是确认号,表明客户端接下来要开始接收来自服务端的第 server_isn+1 个字节的有效数据。

MESSAGE=message 此时可以在报文段中携带客户端到服务端的报文数据;该 ACK 报文段消耗的序号个数等于 message_length(注意 message_length 可以等于0,即不携带有效数据,此时 ACK报文段不消耗序号),下次客户端再向服务端发送的报文段中 SN=client_isn+1+message_length

客户端变为 ESTABLISHED 状态,client——>server 数据流建立。

服务端

接收来自客户端的 ACK 报文段,得知客户端接收能力正常。

变为 ESTABLISHED 状态,server——>client 数据流也建立。

四次挥手

https://cdn.xiaobinqt.cn/xiaobinqt.io/20220326/dd8453fee5ce45579e5022be5763923d.png?imageView2/0/q/75|watermark/2/text/eGlhb2JpbnF0/font/dmlqYXlh/fontsize/1000/fill/IzVDNUI1Qg==/dissolve/52/gravity/SouthEast/dx/15/dy/15
TCP四次挥手

断开连接前,客户端和服务端都处于 ESTABLISHED 状态,两者谁都可以先发起断开连接请求。以下假设客户端先发起断开连接请求。

第一次挥手

客户端

向服务端发送 FIN 报文段FIN=1, SN=client_sn,请求断开连接。

SN=client_sn是序号,表明当前报文段发送的有效数据首字节是从请求建立连接到现在为止客户端向服务端发送的第 client_sn-clien_isn+1 个字节的有效数据。

FIN 报文段虽然不能携带数据,但是会消耗一个序号(相当于发送了1个字节的有效数据),下次客户端再向服务端发送的报文中 SN=client_isn+1

客户端变为 FIN_WAIT1 状态,等待服务端 ACK 报文段。

第二次挥手

服务端

接收来自客户端的 FIN 报文段。

向客户端发送 ACK 报文段ACK=1, AN=client_sn+1, SN=server_sn_wave2,应答客户端的断开连接请求。

SN=server_sn_wave2 是序号,表明当前报文段发送的有效数据首字节是从请求建立连接到现在为止服务端向客户端发送的第 server_sn_wave2-client_isn+1 个字节的有效数据。

AN=client_sn+1 是确认号,表明服务端接下来要开始接收来自客户端的第 client_sn+1 个字节的有效数据。

此时服务端变为 CLOSE_WAIT 状态。

客户端

接收来自服务端的 ACK 包。

变为 FIN_WAIT2 状态,等待服务端关闭连接请求FIN报文段。

第三次挥手

服务端

(服务端想断开连接时)向客户端发送 FIN 报文段FIN=1, SN=server_sn,请求断开连接。

SN=server_sn 是序号,表明当前报文段发送的有效数据首字节是从请求建立连接到现在为止服务端向客户端发送的第 server_sn-clien_isn+1 个字节的有效数据。

FIN 报文段虽然不能携带数据,但是会消耗一个序号(相当于发送了1个字节的有效数据),下次服务端再向客户端发送的报文中 SN=client_isn+2 (若断开连接成功,则服务端不会再向客户端发送下一个报文段)。

第二次挥手和第三次挥手之间,服务端又向客户端发送了 server_sn - server_sn_wave2 个字节的有效数据。

服务端变为 LAST_ACK 状态,等待客户端的 ACK 报文段。

第四次挥手

客户端

接收来自服务端的 FIN 报文段。

向服务端发送 ACK 报文段ACK=1, AN=server_sn+1, SN=client_sn+1,应答服务端断开连接请求。 client_sn+1 是序号,表明当前报文段发送的有效数据首字节是从请求建立连接到现在为止客户端向客户端发送的第 client_isn+1)-clien_isn+1 个字节的有效数据。AN=server_sn+1 是确认号,表明服务端接下来要开始接收来自客户端的第 client_sn+1 个字节的有效数据。

客户端变为 TIME_WAIT 状态,等待2MSL时间后进入 CLOSED 状态,至此 client——>server 数据流被关闭。

服务端

接收来自客户端的 ACK 报文段。

变为 CLOSED 状态,至此 server——>client 数据流被关闭。

Tips
当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据。

常见问题

  • ❓ 为什么建立连接需要“三次”握手

客户端和服务端之间建立的TCP是全双工通信,双方都要确保对方发送能力和接收能力正常。

一次握手后,服务端得知客户端发送能力正常。

二次握手后,客户端得知服务端接收能力和发送能力正常。

三次握手后,服务端得知客户端接收能力正常。

  • ❓ 为什么第四次挥手时要等待2MSL的时间再进入CLOSED状态

MSL(Maximum Segment Lifetime,报文段最大生存时间)是一个未被接受的报文段在网络中被丢弃前存活的最大时间。

保证建立新连接时网络中不存在上次连接时发送的数据包,进入 CLOSED 状态意味着可以建立新连接,等待 >MSL 的时间再进入 CLOSED 状态可以保证建立新连接后,网络中不会存在上次连接时发送出去的数据包。若网络中同时存在发送端在两次连接中发出的数据包,对接收端接收数据可能会有影响。

保证第四次挥手发送的 ACK 能到达接收端,第四次挥手发送的 ACK 可能会出现丢包,另一端接收不到 ACK 会重新发送 FIN。等待 2MSL 的时间可以应对该情况,重发 ACK ,保证另一端能正常关闭连接。

  • ❓ 已经建立了连接,客户端突然出现故障怎么办

TCP 设有一个保活计时器,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为 2 小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔 75 秒钟发送一次。若一连发送 10 个探测报文仍然没反应, 服务器就认为客户端出了故障,接着就关闭连接。

  • ❓ 为什么不能用两次握手进行连接

三次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。

现在把三次握手改成仅需要两次握手,死锁是可能发生的。

比如,计算机 S 和 C 之间的通信,假定 C 给 S 发送一个连接请求分组,S 收到了这个分组,并发送了确认应答分组。按照两次握手的协定,S 认为连接已经成功地建立了,可以开始发送数据分组。 可是,C 在 S 的应答分组在传输中被丢失的情况下,将不知道 S 是否已准备好,不知道 S 建立什么样的序列号,C 甚至怀疑 S 是否收到自己的连接请求分组。在这种情况下,C 认为连接还未建立成功,将忽略 S 发来的任何数据分 组,只等待连接确认应答分组,而 S 在发出的分组超时后,重复发送同样的分组,这样就形成了死锁。

参考