摘要

  • 有效的 CONNECTION_CLOSE 会立即终止 QUIC 连接,并隐式关闭开放流。
  • 发送方进入 closing,接收方进入 draining;两者的响应义务不同。
  • 关闭码和原因短语属于传输证据,不证明业务完成、持久提交或根因。

受保护的 CONNECTION_CLOSE 是来自对端的已认证传输信号。它证明接收端观察到了有效关闭帧,并让连接进入相应的终止路径。但它不会自动变成业务回执。最危险的自动化做法,是看到 NO_ERROR 后把连接上所有未完成的工作都标记为成功。

QUIC 会立即结束传输。开放流会立即关闭,也可以视为被隐式重置。发送关闭帧的一端进入 closing,收到关闭帧的一端进入 draining。这些事实能够说明连接状态,却不能说明应用是否协商过优雅关闭、每条消息是否被接收或处理、持久状态是否已经提交、未完成动作是否得到补偿,也不能说明业务流程已经完成。

记录必须保留帧的变体。0x1c 承载 QUIC 层错误,包括 NO_ERROR,并包含“触发帧类型”字段;该字段未知时记为零。0x1d 承载应用协议错误码,但没有“触发帧类型”字段。传输错误码不是应用回执。NO_ERROR 只表示使用了无错误传输码。原因短语是对端提供的诊断文本,可选、可以为空,而且没有语言标签。它本身不是完整的根因分析。

closing 和 draining 的存在,是为了干净地丢弃延迟或乱序的数据包。通常应至少持续当前 PTO 的三倍;如果另有已记录的机制,能够防止迟到数据包触发响应,则可以更早释放状态。任一状态结束后,连接状态会被丢弃,后续数据包可能得到 Stateless Reset。因此,状态丢弃时间必须与业务完成时间分开记录。

两个状态并不对称。closing 端只保留识别连接数据包和生成 CONNECTION_CLOSE 响应所需的信息,不要求处理收到的帧;在无法验证入站数据包时,还必须遵守放大限制,并对关闭响应进行速率限制。draining 端不得发送数据包;进入 draining 前最多发送一个关闭包,之后必须保持静默。静默是状态机要求,不是业务成功证明。

保护级别同样重要。握手确认后,CONNECTION_CLOSE 必须放在 1-RTT 数据包中发送。确认前,端点可能需要在多个可用保护级别发送关闭帧,以便对端至少处理其中一份。客户端不能假定服务器接受了仅在 0-RTT 中发送的关闭。因此看不到关闭帧,并不自动证明对端忽略了它。每一份关闭副本的保护级别都应进入证据账本。

应用证据必须独立保存:优雅关闭协商、逐项接受、持久提交、补偿和完成。根因归属也需要独立于关闭帧的证据。原因短语可以帮助调查,却不能替代这些记录。无论关闭源于协议违规、主动维护还是未完成工作,边界都一样。