一、有限状态机 FSM
计算机科学家通过一种称为 有限状态机(FSM)的理论工具来解释复杂协议是如何工作的。需要理解以下四个概念:
- 状态(State)。描述在给定的时间机器上的协议软件的特定情况或状态。
- 转换(Transition)。从一种状态转移到另一种状态的行为。
- 事件(Event)。导致状态之间发送转移的事件。
行为(Action)。在事件转移到另一种状态之前对事件做出的响应。
二、TCP 有限状态机(FSM)
TCP 使用三个标志控制 TCP 的状态:
SYN。一个同步消息,用来初始化和建立一个新的连接。功能之一是使两端的序列号进行同步。
- FIN。一个结束消息。发送方想关闭连接。
- ACK。一个确认消息。表示比如 SYN、FIN 消息接收成功。
注: 白色表示客户端,蓝色表示服务端。
状态 | 状态说明 | 事件与转移 |
---|---|---|
CLOSED | - 每个连接建立之前开始的默认状态。 - 该状态是虚构的。 |
被动打开:服务器通过在TCP端口上进行被动打开来开始建立连接的过程。同时,它设置管理连接所需的数据结构(传输控制块,Transmission Control Block,TCB)。然后,它转换为**_LISTEN_** 状态。 |
主动打开,发送SYN:客户端通过发送SYN 消息开始建立连接,并为此连接建立TCB。然后,它转换为**_SYN_SENT_** 状态。 |
||
LISTEN | - 通常是服务端正在等待从客户端接收同步(SYN)消息。 - 它尚未发送自己的SYN消息。 |
接收客户端SYN,发送 SYN + ACK:服务端从客户端接收SYN。它发回包含其自己的SYN的消息,并确认收到的消息。服务端将进入**_SYN_RECEIVED_** 状态。 |
SYN_SENT | - 客户端已发送同步(SYN)消息,并正在等待服务端的匹配SYN。 |
接收 SYN,发送ACK:如果发送 SYN 消息的设备从其他设备接收到一个 SYN,而不是自己的 SYN 的 ACK,它会确认它接收到的 SYN,然后转换到 **_SYN_RECEIVED_** ,等待确认到它的 SYN。 |
接收SYN + ACK,发送 ACK:如果发送 SYN 的设备同时接收到来自其 SYN 的确认以及来自其他设备的 SYN,那么它就确认接收到的 SYN,然后直接进入 _**ESTABLISHED**_ 状态。 |
||
SYN_RECEIVED | 设备既已从其伙伴接收到SYN(连接请求),又发送了自己的SYN。现在,它正在等待对其SYN的ACK,以完成连接设置。 | 接收 ACK:当设备收到对它发送的 SYN 的 ACK 时,它将转换为 _**ESTABLISHED**_ 状态。 |
ESTABLISHED | - 一旦两端进入此状态,就可以自由交换数据。直接某种原因关闭。 |
关闭,发送 FIN: 设备可以通过发送一条发送了FIN (结束)位的消息并转换为 FIN_WAIT_1 状态来关闭连接。 |
接收 FIN:设备可能会从其连接伙伴接收 FIN 消息,要求关闭连接。它将确认此消息并转换为 **_CLOSE_WAIT_** 状态。 |
||
CLOSE_WAIT | - 已从另一端接收 FIN 关闭连接请求。- 此刻必须等待本地设备上的应用程序确认此请求并生成匹配的确认。 |
关闭,发送FIN: - 应用程序收到另一设备发送关闭的请求。当他也需要关闭请求则向 TCP 层发出一个关闭请求。 - TCP 层发送 _FIN_ 请求给另一端。- 状态转换为 _**LAST_ACK**_ 状态。 |
LAST_ACK | - 收到关闭请求并已确认。 - 发送自己的 FIN 并等待对此请求的确认。 |
收到 ACK为FIN:该装置接收用于其关闭请求的确认。现在,我们已经发送了FIN 并进行了确认,并收到了另一台设备的 FIN 并进行了确认,因此我们直接进入 CLOSED 状态。 |
FIN_WAIT_1 | 处于此状态的设备正在等待已发送的FIN的ACK,或者正在等待其他设备发出的连接终止请求。 | 收到ACK 为 FIN: 该装置接收用于其关闭请求的确认。它转换为 FIN_WAIT_2 状态。 |
接收 FIN,发送ACK:设备不会收到 其自身FIN的ACK,而是从其他设备接收 FIN。它确认并进入 CLOSED 状态。 | ||
FIN_WAIT_2 | 处于此状态的设备已收到其终止连接请求的ACK,现在正在等待来自另一设备的匹配FIN。 | 接收FIN,发送ACK: 设备从另一台设备接收FIN。它确认并进入 TIME_WAIT 状态。 |
CLOSING | 该设备已从另一设备接收到FIN并 为其发送了ACK,但尚未收到其自身FIN消息的ACK。 | 收到 ACK为FIN:该装置接收用于其关闭请求的确认。它转换为 TIME-WAIT 状态。 |
TIME_WAIT | 该设备现在已从另一台设备收到 FIN并进行了确认,并发送了自己的 FIN并收到了一个ACK。除了等待确保收到ACK并防止与新连接的潜在重叠之外,我们已经完成了。 | 计时器到期:经过指定的等待时间后,设备将转换为 CLOSED 状态。 |
TCP 状态转换图
三、三次握手流程图
- 客户端 TCP 三次握手状态历程为:
**CLOSE->SYN_SENT->ESTABLISHED**
。 - 服务端三次握手状态历程为:
**CLOSE->LISTEN->SYN_RCVD->ESTABLISHED**
。 TCP三次握手的目的有:
如果两个客户端试图互相访问,则可能发生同时建立连接。这并不常见,并且仅在某些情况下会发生。
- 并不存在 三次握手。相反,就像两个同时的 双向握手。
TCP 建连过程中的参数交换
序列号
- TCP 是基于字节流传输数据。会对每个字节进行序号标识。因此,接收端可能通过确认序列号进行高级操作。
- 序列号不总是以 1 开始,通过 ISN + 随机数生成。
除了序列号之外,其中还包括:
- 窗口比例因子。允许指定比给定的 TCP 窗口字段(默认为16位)更大的窗口容量。
- 允许选择性确认 SACK。仅重发某些丢失的分组。
-
四、四次挥手流程图
客户端主动发出连接释放报文段,并停止再发送数据,主动关闭连接。将控制位置为
1
,序号为**_seq=u_**
。此时客户端进入**_FIN_WAIT_1_**
状态。- 服务端收到连接释放报文段后立即发出确认,确认号是
_**ack=u+1**_
,序号为_v_
,此时服务端进入**_CLOSE_WAIT_**
状态。这里整个 TCP 处于**半关闭**
状态(由于 TCP 是全双工,客户端主动关闭的是其发送数据的功能)。服务端收到客户端的确认后,就进入**_FIN_WAIT_2_**
状态,等待服务端 B 发出的连接释放报文。 - 此时,服务端需要终止连接,因此,主动发送连接释放报文段。发送的报文必须使
**_FIN=1_**
。服务端还必须重复上次已发送过的确认号**_ack=u+1_**
,这里服务端就进入**_LAST_ACK_**
状态,等待客户端的确认报文。 - 客户端收到服务端的连接释放报文段后,必须对此发出确认,且说明确认号
**_ack=w+1_**
,自身序号**_seq=u+1_**
,然后进入**_TIME_WAIT_**
状态。此时,客户端还没有释放掉,必须经过 时间等待计时器(TIME_WAIT timer)设置的时间**_2MSL_**
后,才进入**_CLOSED_**
已关闭状态。 - 客户端经过三种不同状态
- FIN_WAIT_1
- FIN_WAIT_2
- TIME_WAIT
- 服务端经过两种不同状态
- CLOSE_WAIT
- LAST_ACK
- 同时连接终止过程
五、深入理解三次握手
四/五元组
- 四元组。标识一个连接需要 2 个 IP 和 2 个端口号。称为四元组。
-
SYN Queue/Accept Queue
**SYN Queue**
: SYN 队列存储了收到 SYN 包的连接,并且回复SYN+ACK
。在没有收到 ACK 确认报文时进行超时重传,直到超过某个次数停止。Linux 默认值是 5。- 当接收到 ACK 确认报文后,首先在
SYN Queue
队列中找到匹配的数据移除并添加至Accept Queue
队列中,该队列存储的都是状态为ESTABLISHED
的连接。当应用进程调用accept()
时,内核会从Accept Queue
中取出并传递给应用进程。 - 更多信息可用
systemtap
工具获取。 Linux 相关参数配置
加速连续 TCP 连接的数据交互的 TCP 协议扩展。
- 原理
- TCP 三次握手过程中,当用户首次访问服务端时,发送 SYN 包,服务端根据用户 IP 生成 Cookie(已加密),并与 SYN/ACK一同发送。
- 当客户端重连时,在 SYN 包携带 TCP Cookie,如果服务端校验合法,则在用户回复 ACK 前就可以直接发送数据。否则按正常三次握手进行。
Linux 如何开启
核心思想就是当 SYN 队列满后,新的 SYN 不进入队列,由服务端生成 cookie 再以 SYN+ACK中的序列号返回客户端,正常客户端发送报文时,服务器根据报文中携带的 cookie 重新恢复连接。
弊端: 由于 cookie 占用序列号空间,导致此时所有 TCP 可选功能失效,比如扩充窗口、时间戳等。
TCP_DEFER_ACCEPT
当连接状态为
ESTABLISHED
,此时内核可以等到数据到达再激活应用进程接收数据。TCP 握手常用选项
| 类型 | 总长度(字节) | 数据 | 描述 | | —- | —- | —- | —- | | 0 | | | 选项列表末尾标识 | | 1 | - | - | 无意义,用于32位对齐使用 | | 2 | 4 | MSS值 | 握手时发送端告知可以接收的最大报文段大小 | | 3 | 3 | 窗口移位 | 指明最大窗口扩展后的大小 | | 4 | 2 | - | 表明支持 SACK 选择性确认中间报文段功能 | | 5 | 可变 | 确认报文段 | 选择性确认窗口中间的报文段 | | 8 | 10 | 时间戳 | 用于更精准的计算 RTT,及解决 PAWS 问题 | | 14 | 3 | 校验和算法 | 双方认可后,可使用新的校验和算法 | | 15 | 可变 | 校验和 | 当 16 位标准校验和放不下时,放置在这里 | | 34 | 可变 | FOC | TFO 中 Cookie |
三次握手必要性
- 确保两端建立正确的初始序列号。
- 防止已失效的连接请求突然又传到了服务端,因而产生错误。
相关RFC文档:
原理:
- 攻击端发送完第一次握手数据包(即 SYN)后就 “消失” 了,服务端就会不断地发送发送第二次握手数据包(即 SYN+ACK),此时该连接处于
半连接
状态,由于服务器无法收到该请求的 ACK ,直到超时后丢弃。 - 当同时存在大量这样的连接时,服务器网络资源被耗尽,无法接收真正的 TCP 请求。
- 攻击端发送完第一次握手数据包(即 SYN)后就 “消失” 了,服务端就会不断地发送发送第二次握手数据包(即 SYN+ACK),此时该连接处于
- TCP 洪泛攻击复用 TCP 三次握手机制的缺陷。
防范:
Maximum Segment Lifetime,MSL。最长报文段寿命。
- 属于 TCP 协议范围内概念,指的是一个 TCP 的 Segment 在网络上生存的最大时间,与 TTL 不绝对相等。TCP 使用 MSL 作为前提假设(assume)进行协议上的设计。
RFC793 定义时间为 2分钟,但是 Linux 设置时间为 30秒,且不可更改。
TIME_WAIT 存在的意义
只能主动发起
FIN
的一方才会存在TIME_WAIT
状态。提供足够多的时间以确保 ACK 能被接收端接收,并在丢失时重新发送。
在此连接结束与任何后续连接之间提供一个 缓冲时间。允许旧的重复的报文段在网络中消失,对新连接不构成影响。
TIME_WAIT 造成的影响
TIME_WAIT
会长时间占用(2*MSL
)一个四元组连接,这可能会导致后续相同元组的连接创建失败。2*MSL
期间内核需要维持该SOCKET
的数据结构,因此,数据过多的话会消耗内存,并且增加内核遍历有关hash table
的时间,从而降低性能。net.ipv4.tcp_timestamps
- net.ipv4.tcp_tw_reuse
- 对处于
TIME_WAIT
的 sockets 启用重用功能,使得新建连接可以重用TIME_WAIT
状态的四元组。 - 默认只对回环网卡启用。
- 只适用于出站连接。
- 利用时间戳解决旧连接延迟报文到达新连接问题。
- 对处于
- net.ipv4.tcp_tw_recycle
- Linux 4.12 废弃。
tcp_max_tw_buckets
服务器永远不要主动断开连接,让客户端去做这件事情,由客户端承担
TIME_WAIT
。- 如果你的服务端既要建立出站连接又要建立入站连接,黄金法则依然是:如果 TIME_WAIT 注定要发生,那么就让它发生在对端。如果对端超时了,直接使用 RST 终止连接。但你服务端也要避免发起频繁的短时连接,尽量使用长连接或者连接池。
如果你是客户端,尽量使用长连接和连接池,并发扬风格主动断开连接。
七、TCP 的 Keep-Alive 功能
Linux 的 Keep-Alive
如果设备处于
LISTEN
状态,则FIN
将被忽略,设备保持原有状态。- 如果设备处于
SYN_RECEIVED
状态,此状态从LISTEN
状态转换过来,则设备将会回退为LISTEN
状态。 - 在任何其他状态下,重置都会导致连接中止,并且设备将返回此连接的
关闭
状态。并通知上层应用进程连接已关闭。九、补充 TCP 标准
| RFC | 名称 | 描述 | | —- | —- | —- | | 813 | TCP中的窗口和确认策略 | 讨论TCP滑动窗口确认系统,描述可能发生的某些问题以及纠正方法。 | | 879 | TCP最大段大小和相关主题 | 讨论控制TCP消息大小的重要最大段大小(MSS)参数,并将此参数与IP数据报大小相关联。 | | 896 | IP / TCP Internetworks中的拥塞控制 | 讨论拥塞问题以及如何使用TCP处理拥塞问题。
注意普通协议套件名称的有趣反转:“IP/ TCP”。 | | 1122 | Internet主机的要求-通信层 | 描述如何在主机上实现TCP的重要细节。 | | 1146 | TCP备用校验和选项 | 指定一种机制,使TCP设备使用校验和生成的替代方法。 | | 1323 | TCP高性能扩展 | 定义用于高速链接的TCP扩展和新的TCP选项。 | | 2018 | TCP选择性确认选项 | 基本TCP功能的增强,它允许TCP设备有选择地指定特定段进行重传。SACK。 | | 2518 | TCP拥塞控制 | 描述了用于TCP网络中拥塞控制的四种算法:
- 慢启动。
- 拥塞避免。
- 快速重传。
- 快速恢复。
| | 2988 | 计算TCP的
重传计时器 | 讨论与设置TCP重传计时器有关的问题,该计时器控制设备在重传数据之前等待发送数据确认的时间。 |
九、MSS
- Max Segment Size,MSS。
- 仅指 TCP 承载数据,不包含 TCP 头部大小。详见 RFC 879。
- MSS 选择目的
- 尽量每个报文段携带更多的数据,以减少头部空间占用比率。
- 防止段被某个设备的 IP 层基于 MTU 拆分。
- 相关默认值
- 默认的 IP最大数据报 MTU 大小为 576。(IP头部 20个字节,TCP 头部 20个字节)。
- 默认的 CP最大段大小 为 536。
- 握手阶段协商确定
MSS
。- 类型: 4,总长度: 4,数据: MSS值。
- 分类:
**SMSS,Sender Maximum Segment Size**
,发送方可发送的最大报文段。**RMSS,Receiver Maximum Segment Size**
,接收方可接收的最大报文段。
五、多路复用
- 在一个信道上传输多路信号或数据流的过程和技术。
- 分类
- 频分多址。FDMA。
- 时分多址。TDMA。
- 码分多址。CDMA。
- epoll + 非阻塞 socket
- Linux 2.5.44
- 进程内同时刻找到缓冲区或者连接状态变化的所有 TCP 连接。
- API
- epoll_create()
- epoll_ctl()
- epoll_wait()
- 高效原因: 活跃连接只占总连接的一小部分。
- 非阻塞 + epoll + 同步编程 = 协程