NNRP/1 传输策略与探测
这不是某个局部实现的优化技巧,而是协议本身必须讲清楚的能力边界。
为什么不能把传输层写死
现实网络并不总是奖励 UDP / QUIC。以中国市场为例,一些运营商并不愿意兼容适配 UDP 业务,会把大量 UDP 流量识别为 PCDN 流量,从而触发限速、惩罚甚至封禁。其他国家和网络环境里,也可能出现类似的商业策略、网络治理策略或设备兼容问题。
如果一个现代应用层协议把自己硬绑定到单一 transport,它的可达性、吞吐量和稳定性就会直接受制于局部网络政策。NNRP 的目标恰恰相反:把提交、结果、流控和状态语义稳定在应用层,再根据网络条件选择最合适的 transport binding。
协议里长什么样
NNRP 不通过“每种 transport 一个新 scheme”的方式表达选路,而是把 transport 策略做成显式协议语义:
- endpoint 只保留一个安全入口
nnrps://,不把 QUIC、TCP 等 binding 写进 URI scheme。 - 主握手前可以执行
TRANSPORT_PROBE / TRANSPORT_PROBE_ACK,用接近真实提交载荷大小的样本测 RTT、抖动和吞吐。 CLIENT_HELLO可携带transport_policy与preferred_transport_id,表达自动选择、偏好某条路径或强制某条路径。SERVER_HELLO_ACK返回被接受的策略和最终生效的active_transport_id,让选路结果变成协议可见事实。- 如果链路质量变化,
SESSION_MIGRATE / SESSION_MIGRATE_ACK允许在不同 binding 之间延续同一 session,而不是只能断开重连再重建全部上下文。
最小探测时序图
探测到底怎么做
最小实现不需要把 probe 做成复杂测速系统,但至少要遵守下面这条顺序:
- 先根据本地 dial policy 过滤候选 binding,例如
force_tcp时直接跳过 QUIC,不做无意义探测。 - 对每条候选路径发送一组
TRANSPORT_PROBE,每组至少带上probe_id、接近真实业务的sample_size,以及避免偶然值的sample_count。 - 等每条路径返回
TRANSPORT_PROBE_ACK后,收集往返时间、抖动、有效吞吐和服务端给出的丢包或限速提示。 - 按统一排序规则比较候选路径,选出本次连接真正要进入主握手的 binding。
- 把选中的
preferred_transport_id带进CLIENT_HELLO,并以SERVER_HELLO_ACK返回的active_transport_id作为最终事实。
探测至少要比较什么
probe 不是只看一个 RTT 数字,而是至少要比较四类信号:
- 可达性:这条路径能不能稳定收发 probe,而不是偶发通一次就算成功。
- 延迟稳定性:不仅看平均 RTT,还要看抖动和尾延迟,避免选到“均值好看、抖动很大”的路径。
- 接近真实载荷时的有效吞吐:样本体量要尽量贴近真实提交,否则测出来的只是小包友好度。
- 退化信号:包括超时、重传、显式
drop_hint、服务端限速提示,以及连续 probe 的成功率。
为什么 probe 不能只是 ping
仅看 ICMP ping 或极小包 RTT,无法反映真实业务流量会遭遇的限速策略。很多网络对小包宽松,对大体量 UDP 或持续流量严格得多。
因此,probe 的关键不是“能不能通”,而是“在接近真实载荷体量时,哪条路径的吞吐、抖动和恢复表现更好”。这也是为什么 TRANSPORT_PROBE 的 body 要尽量接近真实提交数据量,而不是只发一个很小的心跳包。
宿主侧会看到什么
从宿主或客户端视角,通常会经历下面这条链路:
- 本地 dial policy 先决定是
auto、prefer_quic、prefer_tcp还是force_*。 - 如果策略允许自动选路,客户端先在候选 binding 上跑 probe。
- 选出更合适的路径后,再在该路径上执行
CLIENT_HELLO / SERVER_HELLO_ACK与后续 session 建立。 - 若运行中网络退化,客户端可以重新探测并发起
SESSION_MIGRATE;若迁移失败,再退回到“新建连接 + 新建 session”的保底路径。
为什么这必须是协议能力
这件事不能只留给本地选路逻辑,因为它至少涉及四个协议级一致性问题:
- 客户端与服务端都要看得见 transport 策略和最终结果,不能只靠本地猜测。
- 所有客户端实现都需要在相似网络条件下做出相近决策,否则同一协议会表现出实现依赖行为差异。
- 观测、审计和故障定位必须能记录“探测了什么、选了什么、为什么迁移”,这需要统一的协议事件语义。
- 未来不只是 transport binding,更多内部组件也可能需要策略模式化;如果 transport 已经被正确放进协议层,后续扩展会更干净。