Network - TCP Connection Management
Created by : Mr Dk.
2021 / 03 / 09 11:11
Nanjing, Jiangsu, China
Basic
TCP 提供了一种 面向连接的 (connection-oriented)、可靠的字节流 服务。字节流中并不存在 TCP 自动插入的消息边界,由每个端点独立选择自己的读写大小。TCP 会把应用程序字节流转换为一组 IP 层可以携带的分组,每个分组都有唯一的序列号,用于标识顺序,以保证字节流有序。
TCP 的关键技术:
- 连接管理 (三次握手、四次挥手)
- 超时重传
- 流量控制 (发送方与接收方的速率同步问题)
- 拥塞控制 (发送方与接收方之间的网络速率问题)
- 保活 (Keep Alive)
TCP Connection
TCP 连接由一个 四元组 组成:
- 源 IP 地址
- 源端口号
- 目的 IP 地址
- 目的端口号
TCP 连接的三个阶段:
- 启动 (连接启动)
- 数据传输
- 退出 (连接关闭)
Establishment of TCP Connection
(CLIENT) (SERVER)
| |
| | LISTEN
| -------->>>>>>>> SYN, Seq = ISN(c) -------->>>>>>>> |
SYN_SENT | | SYN_RCVD
| <<<--- SYN + ACK, Seq = ISN(s), ACK = ISN(c) + 1 <<<--- |
ESTABLISHED | |
| --->>> ACK, Seq = ISN(c) + 1, ACK = ISN(s) + 1 --->>> |
| | ESTABLISHED
SYN
和 ACK
都是 TCP 头部中的标志位,Seq
(序列号) 和 ACK
带有的确认号都是 TCP 头部中的组成部分。
序列号表示当前传送的 TCP 分组编号,实际上是当前分组数据的第一个字节在整个字节流中的偏移位置,是一个 32-bit 无符号数,溢出后重新归零。在连接建立时,并不是使用 0 或 1 来作为第一个分组的编号,而是通过一些较为复杂的算法,随机生成一个初始序列号 ISN
。理论上只有 IP 地址、端口号、序列号、校验和 四个要素都正确的分组才会被接收方接收。随机产生初始序列号有助于处理一个 TCP 连接关闭后又以相同的四元组被重新打开时,序列号之间发生重合的问题 (防止两次独立的连接传输的数据互串)。
ACK
报文中带有的确认号表示接收方期待接收的下一个序列号,也就是最后一个被成功接收的字节的序列号 + 1。每发送一个新的 SYN
,就期待收到一个序列号 + 1 的 ACK
确认,因为 SYN
的数据占一个字节。由于 SYN
占用了一个序列号,因此将会被 重传机制 保证可靠交付,如果丢失则重传,重传时以 指数回退 的方式逐渐降低发送频率。
TCP 连接建立的步骤:
- 客户端发送一个
SYN
报文,指明客户端生成的初始序列号ISN(c)
- 服务器也发送一个
SYN
报文,指明服务端生成的初始序列号ISN(s)
;同时,还要对客户端的SYN
进行确认,发送带有ISN(c) + 1
的ACK
报文 - 客户端对服务器的
SYN
报文进行确认,发送带有ISN(s) + 1
的ACK
报文
最大段大小 (MSS) 选项
MSS
指的是 TCP 协议允许从对方接收到的最大报文段,也是对方在发送数据时能够使用的最大报文段。在 TCP 连接建立时,通信双方需要在 SYN
报文中的 MSS
选项里说明自己允许接收的最大报文段大小。
用户超时选项
TCP 发送者在收到对方确认接收数据前,愿意等待 ACK
确认的时间。
Termination of TCP Connection
(CLIENT) (SERVER)
| |
ESTABLISHED | | ESTABLISHED
| ------>>>>>> FIN + ACK, Seq = K, ACK = L ------>>>>>> |
FIN_WAIT_1 | |
| <<<<<<------ ACK, Seq = L, ACK = K + 1 <<<<<<------ |
FIN_WAIT_2 | | CLOSE_WAIT
| <<<<<<------ FIN + ACK, Seq = L, ACK = K + 1 <<<<<<------ |
TIME_WAIT | | LAST_ACK
| ------>>>>>> ACK, Seq = K, ACK = L ------>>>>>> |
(2 MSL) | | CLOSED
| |
CLOSED | |
连接的任何一方都可以发起关闭操作:
- 主动关闭方发送
FIN
,指明自己当前的序列号 K,以及确认最近接收到的数据,希望下一个接收到的报文序列号为 L - 被动关闭方发送
ACK
来确认FIN
,序列号为 L (如主动方所愿),确认接收到的报文为 K + 1 号 - 被动关闭方将自身转变为主动关闭方,发送
FIN
,指明自身序列号为 L (因为刚才的ACK
不占序列号),确认接收到的报文为 K + 1 (之前另一方没有发送报文) - 主动关闭方发送
ACK
回应被动关闭方的FIN
(但是序列号为什么还是 K 😧)
被动关闭方收到 ACK
后立刻进入关闭状态;主动关闭方在 TIME_WAIT
状态下等待两个 MSL (最大段生存期,Maximum Segment Lifetime) 后进入关闭状态。MSL 代表任何报文段在被丢弃前在网络中被允许存在的最长时间。等待这个时间,是要确保能够接收到对方主动重传的 FIN
:如果自己发出的最后一个 ACK
丢失,那么对方将重新发送 FIN
,那么在 2 MSL 的时间内将能等到这个 FIN
,然后向对方重新发送 ACK
。这样使得 TCP 避免丢失最终的 ACK。
等待 2 MSL 的另一个原因是,防止以相同四元组建立的新连接中,收到了上一次连接中延迟到达的数据。说白了就是,在 2 MSL 超时之前,当前四元组不可重新使用。等到确认该四元组中的数据交换已经结束后,该四元组才能重新被使用。当连接处于 2 MSL 等待状态时,任何延迟到达的报文段都会被丢弃。2 MSL 状态能够防止新的连接将前一个连接的延迟报文解释成自身数据的情况。
通常,服务器执行被动关闭操作,不会进入 TIME_WAIT
状态。
有时,重启服务器后出现的 端口已被占用 无法绑定,就是因为服务器执行了主动关闭后,四元组还处于 2 MSL 等待状态。
同时也可以看出,通信双方关闭连接的过程中,两个方向可以被独立拆分。因此 TCP 支持 半关闭:仅关闭一个方向上的数据传输:
shutdown()
关闭一个方向上的传输close()
同时关闭两个方向上的传输
重置报文
当接收方收到一条对于相关连接而言是不正确的报文,就会返回一个 重置报文段 RST
,使 TCP 连接快速拆卸:
- 对不存在端口的连接请求
- 关闭连接,终止释放 (不同于
FIN
的 有序释放) - 半开连接 (在未告知通信另一端的情况下关闭或终止连接,然后重新连接),由于重新启动的服务对于当前 TCP 连接没有记忆,则会回复一个
RST
- 时间等待错误 (在 2 MSL 期间收到了延时到达的数据,发送
ACK
后得到RST
,使得客户端过早转移到CLOSED
状态)
TCP Server Options
TCP 服务器默认监听的本地 IP 地址是一个通配符::::22
,表示将接受到所有本地 IP 地址的连接。当然,可以指定监听某个本地 IP 地址,那么只有访问该 IP 地址的连接会被接受。TCP 依靠四元组的多路分解获得报文段。拒绝连接的操作由 OS 的 TCP 协议栈根据请求 SYN
报文中的目的地址判断。
另外,OS 还可以对连接的外部 IP 和端口进行过滤。
操作系统内会有两种队列维护新被建立的连接:
- 已经收到
SYN
,但连接还没有完成 (未收到ACK
,处于SYN_RCVD
状态) - 三次握手已经完成,但还未被应用程序接受 (应用程序中
accept()
还未返回)
OS 可以对这两个队列的长度进行配置。如果队列已满,那么再进入的连接将会被拒绝。如果三次握手已经完成而连接还未被应用程序接受,但此时客户端的数据已经到达,那么 OS 会将到来的数据缓存。如果队列中已经没有足够的空间分配给新连接,OS 可以试着 延迟 对 SYN
作出响应,从而给应用程序一个跟上节奏的机会。Linux 坚持在能力允许的范围内不忽略进入的连接。
References
TCP/IP 详解 - 卷 1:协议