XON/XOFF 是什么?

XON/XOFF(也称为 DC1/DC3 流控)是一种软件流控制(Software Flow Control)机制。它通过在数据流中嵌入特殊控制字符来协调发送方和接收方的数据传输速度,防止接收方缓冲区溢出。


核心概念:两个神奇的控制字符

XON/XOFF 使用 ASCII 控制字符集中的两个特定代码:

名称

十六进制

十进制

ASCII 名称

键盘对应

功能

XON

0x11

17

DC1 (Device Control 1)

Ctrl+Q

恢复传输(Transmit ON)

XOFF

0x13

19

DC3 (Device Control 3)

Ctrl+S

暂停传输(Transmit OFF)

历史渊源:DC1~DC4 最初是为电传打字机(Teletype)设计的设备控制码,DC1/DC3 被用来远程控制纸带阅读机的启停,后来成为串口软件流控的事实标准。


工作原理(带内信令)

XON/XOFF 是带内信令(In-band Signaling),即控制信息和用户数据共用同一条通道

┌─────────┐                    ┌─────────┐
│ 发送方  │ ───(数据+XON/XOFF)──→ │ 接收方  │
│         │ ←──(数据+XON/XOFF)─── │         │
└─────────┘                    └─────────┘

具体流程

接收方缓冲区状态          接收方动作              发送方响应
─────────────────────────────────────────────────────────────
缓冲区正常  (< 80%)       不发送控制字符          正常发送数据
缓冲区将满  (≥ 80%)       发送 XOFF (0x13)       暂停发送数据
缓冲区有空  (< 30%)       发送 XON (0x11)        恢复发送数据

详细步骤

  1. 正常传输:发送方持续发送数据

  2. 接收方压力大:当接收方输入缓冲区快满(如达到80%),立即向发送方发送一个 XOFF (0x13) 字符

  3. 发送方暂停:发送方收到 XOFF 后,立即停止发送(完成当前正在传输的字节后停止)

  4. 接收方处理:接收方处理完缓冲区数据,腾出空间

  5. 恢复传输:当缓冲区降到安全水平(如30%),接收方发送 XON (0x11) 字符

  6. 发送方继续:发送方收到 XON 后,恢复数据发送


关键设计:滞后区间(Hysteresis)

为了避免频繁启停导致的震荡(刚发 XON 又触发 XOFF),通常设置滞后区间

缓冲区使用率
100% ┤
     │      XOFF触发点 (80%)
 80% ┤──────────┐
     │          │    安全区间
     │          │    (不触发)
 30% ┤      ┌───┘
     │      │ XON恢复点 (30%)
  0% ┼──────┴────────────────
  • XOFF 触发点:80%(缓冲区将满时喊"停")

  • XON 恢复点:30%(缓冲区有足够空间后喊"继续")

  • 安全区间:30%~80% 之间不发送控制字符,避免频繁切换


硬件流控 vs 软件流控

特性

RTS/CTS 硬件流控

XON/XOFF 软件流控

信号线

需要额外 2 根线(RTS/CTS)

只需 3 根线(TX/RX/GND)

实现方式

硬件电平信号(带外信令)

特殊字符(带内信令)

响应速度

极快(硬件立即响应)

稍慢(需解析字符)

带宽占用

不占用数据带宽

占用带宽(XON/XOFF 字符混入数据流)

二进制数据

支持(无限制)

有风险(数据中不能含 0x11/0x13)

线路成本

线缆更复杂

线缆更简单(成本低)

可靠性

(不受数据内容影响)

较低(噪声可能导致控制字符丢失)

兼容性

需要双方硬件支持

纯软件实现,兼容性好


XON/XOFF 的优点

  1. 极简布线:只需要 TX、RX、GND 三根线,适合线缆受限的场景

  2. 成本低:早期计算机时代,减少线缆意味着显著降低成本

  3. 纯软件实现:不需要额外的硬件引脚,任何 UART 都支持

  4. 广泛兼容:几乎所有串口通信软件都内置支持


XON/XOFF 的缺点

1. 带内信令的致命缺陷

控制字符混入数据流,如果传输的二进制数据恰好包含 0x110x13,会被误认为是流控命令!

示例:传输二进制文件时
原始数据:[0x11] [0x48] [0x65] [0x6C] [0x6C] [0x6F]
          ↑
        被误认为 XON,导致传输异常!

解决方案:数据转义(Escape)

  • 发送前将数据中的 0x11/0x13 转义为其他序列(如 0x7D 0x31

  • 接收端反向解码

  • 代价:增加带宽开销和编解码复杂度

2. 字符丢失风险

  • 在嘈杂的线路上,XON/XOFF 字符可能丢失或损坏

  • 如果 XOFF 丢失:接收方缓冲区溢出,数据丢失

  • 如果 XON 丢失:发送方永久暂停,通信死锁

  • 缓解措施:多次重复发送 XON/XOFF

3. 带宽浪费

  • 每个 XON/XOFF 周期都消耗 1~2 字节的带宽

  • 频繁流控时,有效数据率降低

4. 延迟问题

  • 接收方发送 XOFF 后,发送方可能还在传输数据(已发送但未到达的字节 + 正在传输的字节)

  • 需要预留足够的缓冲区应对这段"飞行中"的数据


在 usb-serial-for-android 中的使用

UsbSerialPort port = ...;

// 打开串口时启用 XON/XOFF 软件流控
port.open(connection);
port.setParameters(
    115200,           // 波特率
    UsbSerialPort.DATABITS_8,
    UsbSerialPort.STOPBITS_1,
    UsbSerialPort.PARITY_NONE,
    true              // xonxoff = true 启用软件流控
);

// 注意:启用 XON/XOFF 后,库会自动处理 0x11/0x13 字符
// 应用层无需(也不应该)手动发送这些字符

手动发送(不推荐,除非你知道在做什么)

// 发送 XOFF 请求对方暂停
byte[] xoff = new byte[] { 0x13 };
port.write(xoff, 1000);

// 稍后发送 XON 请求恢复
byte[] xon = new byte[] { 0x11 };
port.write(xon, 1000);

一句话总结

XON/XOFF = "用 Ctrl+Q 和 Ctrl+S 控制数据传输的暂停和播放"

  • 优点:省线、简单、成本低,3 根线就能做流控

  • 缺点:不能传二进制文件(会误触发),可靠性不如硬件流控

现代建议

  • 优先用 RTS/CTS:更可靠,特别是高速传输或二进制数据

  • XON/XOFF 备用:只有 TX/RX/GND 三根线可用时才考虑

  • 混合使用:某些场景可同时启用两种流控,互为备份