一、有限状态机 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。现在,它正在等待对其SYNACK,以完成连接设置。 接收 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 并等待对此请求的确认。
收到 ACKFIN该装置接收用于其关闭请求的确认。现在,我们已经发送了FIN 并进行了确认,并收到了另一台设备的 FIN 并进行了确认,因此我们直接进入 CLOSED 状态。
FIN_WAIT_1 处于此状态的设备正在等待已发送的FINACK,或者正在等待其他设备发出的连接终止请求。 收到ACK FIN 该装置接收用于其关闭请求的确认。它转换为 FIN_WAIT_2 状态。
接收 FIN,发送ACK设备不会收到 其自身FINACK,而是从其他设备接收 FIN。它确认并进入 CLOSED 状态。
FIN_WAIT_2 处于此状态的设备已收到其终止连接请求的ACK,现在正在等待来自另一设备的匹配FIN 接收FIN,发送ACK 设备从另一台设备接收FIN。它确认并进入 TIME_WAIT 状态。
CLOSING 该设备已从另一设备接收到FIN并 为其发送了ACK,但尚未收到其自身FIN消息的ACK 收到 ACKFIN该装置接收用于其关闭请求的确认。它转换为 TIME-WAIT 状态。
TIME_WAIT 该设备现在已从另一台设备收到 FIN并进行了确认,并发送了自己的 FIN并收到了一个ACK。除了等待确保收到ACK并防止与新连接的潜在重叠之外,我们已经完成了。 计时器到期:经过指定的等待时间后,设备将转换为 CLOSED 状态。

TCP 状态转换图

TCP有限状态机.png

三、三次握手流程图

image.png

  • 客户端 TCP 三次握手状态历程为: **CLOSE->SYN_SENT->ESTABLISHED**
  • 服务端三次握手状态历程为: **CLOSE->LISTEN->SYN_RCVD->ESTABLISHED**
  • TCP三次握手的目的有:

    • 联系和通信。
    • 序列号同步。
      • 规定 SYN 报文需要消耗一个序列号。
    • 交换TCP窗口大小信息。
    • 参数交换。控制 TCP 连接操作的某些参数。比如:
      • 交换 TCP 滑动窗口大小。

        探究同时建立连接

  • 如果两个客户端试图互相访问,则可能发生同时建立连接。这并不常见,并且仅在某些情况下会发生。

  • 并不存在 三次握手。相反,就像两个同时的 双向握手。

TCP 同时建立连接示意图.png

TCP 建连过程中的参数交换

序列号

  • TCP 是基于字节流传输数据。会对每个字节进行序号标识。因此,接收端可能通过确认序列号进行高级操作。
  • 序列号不总是以 1 开始,通过 ISN + 随机数生成。

序列号同步.png
除了序列号之外,其中还包括:

  • 窗口比例因子。允许指定比给定的 TCP 窗口字段(默认为16位)更大的窗口容量。
  • 允许选择性确认 SACK。仅重发某些丢失的分组。
  • 备用校验和方法。

    四、四次挥手流程图

    TCP 四次挥手.png
    tcpclose.png

  • 客户端主动发出连接释放报文段,并停止再发送数据,主动关闭连接。将控制位置为 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
  • 同时连接终止过程

TCP同时连接终止过程.png

五、深入理解三次握手

四/五元组

  • 四元组。标识一个连接需要 2 个 IP 和 2 个端口号。称为四元组。
  • 五元组。四元组 + 协议类型就称为 五元组。

    SYN Queue/Accept Queue

    image.png

  • **SYN Queue**: SYN 队列存储了收到 SYN 包的连接,并且回复 SYN+ACK 。在没有收到 ACK 确认报文时进行超时重传,直到超过某个次数停止。Linux 默认值是 5。

  • 当接收到 ACK 确认报文后,首先在 SYN Queue 队列中找到匹配的数据移除并添加至 Accept Queue 队列中,该队列存储的都是状态为 ESTABLISHED 的连接。当应用进程调用 accept() 时,内核会从 Accept Queue 中取出并传递给应用进程。
  • 更多信息可用 systemtap 工具获取。
  • Linux 相关参数配置

    • SYN_RCV(服务端)
      • **net.ipv4.tcp_max_syn_backlog:** SYN_RCVD 状态连接的最大个数。
      • **net.ipv4.tcp_synack_retries:** 被动建立连接时,发送 SYN/ACK 的重试次数。
    • SYN_SENT(客户端)
      • **net.ipv4.tcp_syn_retries=6:** 主动建立连接时,发送 SYN 的重试次数。
      • **net.ipv4.ip_local_port_range=32768 60999:** 建立连接时的本地端口可用范围。

        TCP Fast Open (TFO)降低时延(Cookie)

  • 加速连续 TCP 连接的数据交互的 TCP 协议扩展。

  • 原理
    • TCP 三次握手过程中,当用户首次访问服务端时,发送 SYN 包,服务端根据用户 IP 生成 Cookie(已加密),并与 SYN/ACK一同发送。
    • 当客户端重连时,在 SYN 包携带 TCP Cookie,如果服务端校验合法,则在用户回复 ACK 前就可以直接发送数据。否则按正常三次握手进行。
  • Linux 如何开启

    • net.ipv4.tcp_fastopen: 开启 TFO 功能。 0:关闭/1:作为客户端可以使用TFP/2:作为服务端可以使用TFP/3: 无论何种情况都可以使用 TFO。

      TCP_SYNCOOKIES

  • 核心思想就是当 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文档:

    • RFC793、RFC1122、RFC3168、RFC6093、RFC6528。

      SYN 重复发送

      image.png
      TCP 通过 FIN 报文来解决 SYN 重复发送问题。上述图描述的是 RST 比 重复发送的 SYN 早到的情况。如果 RSTSYN 晚到,则 RST 在客户端和发送端会有更复杂的交换。

      TCP 洪泛攻击

  • 原理:

    • 攻击端发送完第一次握手数据包(即 SYN)后就 “消失” 了,服务端就会不断地发送发送第二次握手数据包(即 SYN+ACK),此时该连接处于 半连接 状态,由于服务器无法收到该请求的 ACK ,直到超时后丢弃。
    • 当同时存在大量这样的连接时,服务器网络资源被耗尽,无法接收真正的 TCP 请求。
  • TCP 洪泛攻击复用 TCP 三次握手机制的缺陷。
  • 防范:

    • 降低 SYN 超时时间,使得主机尽快释放半边接所占用的资源。
    • 采用 TCP SYNCOOKIES 技术。
    • 利用防火墙进行拦截。

      六、深入理解四次挥手

      TIME_WAIT(2MSL 存在的合理性)

      image.png

      MSL

  • 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 的时间,从而降低性能。

    • **TIME_WAIT** 所占用内存很少,涉及 Linux 两个内核数据结构。
      • HashTable1: 保存所有连接。既包含 TIME_WAIT 状态,也包含其他状态的 TCP 连接。主要用于有新的数据到达时,快速从这个 HashTable 中查找确切连接。
      • HashTable2: 保存所有的出/入端口,用于快速查找一个可用的端口或随机端口。

        与 TIME_WAIT 有关的几个参数

  • 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

    • 默认 262144。
    • 控制并发的 TIME_WAIT 状态连接数量。如果超出则会把多的销毁。
    • 对端没有收到最后的 ACK,则会重发 FIN 报文,由于服务端没有相关的连接信息,收到 FIN 后回一个 RST 报文将连接重置。
    • 问题: 如果一个相同的四元组连接随即被创建,而恰好旧连接的报文到达了,这会让当前的连接发生数据错乱。

      小结

  • 服务器永远不要主动断开连接,让客户端去做这件事情,由客户端承担 TIME_WAIT

  • 如果你的服务端既要建立出站连接又要建立入站连接,黄金法则依然是:如果 TIME_WAIT 注定要发生,那么就让它发生在对端。如果对端超时了,直接使用 RST 终止连接。但你服务端也要避免发起频繁的短时连接,尽量使用长连接或者连接池。
  • 如果你是客户端,尽量使用长连接和连接池,并发扬风格主动断开连接。

    七、TCP 的 Keep-Alive 功能

  • Linux 的 Keep-Alive

    • 发送心跳周期。net.ipv4.tcp_keepalive_time=7200
    • 探测包发送间隔。net.ipv4.tcp_keepalive_intvl=75
    • 探测包重试次数。net.ipv4.tcp_keepalive_probes=9

      八、FIN

      当设备收到 RST 的分组时,它告诉设备重置连接,以便可以重新建立连接。若重置有效,则消息处理取决于接收消息的设备的状态:
  • 如果设备处于 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**,接收方可接收的最大报文段。

image.png

五、多路复用

  • 在一个信道上传输多路信号或数据流的过程和技术。
  • 分类
    • 频分多址。FDMA。
    • 时分多址。TDMA。
    • 码分多址。CDMA。
  • epoll + 非阻塞 socket
    • Linux 2.5.44
    • 进程内同时刻找到缓冲区或者连接状态变化的所有 TCP 连接。
    • API
      • epoll_create()
      • epoll_ctl()
      • epoll_wait()

image.png

  • 高效原因: 活跃连接只占总连接的一小部分。
  • 非阻塞 + epoll + 同步编程 = 协程

image.png