ICE提名机制:常规提名与激进提名
ICE提名,说白了就是决定「用哪条路」的最后一步。
我在做WebRTC开发的头两年,一直觉得提名是个很玄乎的东西。直到有一次排查一个通话卡顿问题,发现两边明明连通了,却一直在换路径——嗯,就是提名机制没搞对。
今天咱们就把这个机制彻底讲清楚。
提名是什么?为什么需要它?
ICE流程走到最后,候选者对(Candidate Pair)已经排序好了。但排序归排序,到底用哪一对,得有个最终确认的动作。这个动作,就叫提名(Nomination)。
你可以这么理解:
- 连通性检查(Connectivity Check)是「探路」
- 排序(Priority)是「给路打分」
- 提名(Nomination)是「拍板定路」
没有提名,ICE永远在「待定」状态。只有提名完成,媒体流才能真正开始跑。
核心要点:提名是ICE从「检查阶段」进入「完成阶段」的开关。
两种提名模式:常规 vs 激进
RFC 5245里定义了两种提名方式。我刚开始看文档时觉得区别不大,直到自己动手实现了一遍才明白——这俩完全是不同的哲学。
| 特性 | 常规提名(Regular) | 激进提名(Aggressive) |
|---|---|---|
| 提名时机 | 所有检查完成后,选最优的提名 | 边检查边提名,发现可用就提名 |
| 速度 | 慢,但稳定 | 快,但可能反复 |
| 提名次数 | 通常只提名一次 | 可能多次提名不同候选者 |
| 适用场景 | 对稳定性要求高的场景 | 对连接速度要求高的场景 |
常规提名(Regular Nomination)
常规提名的逻辑很简单:先做完所有连通性检查,等所有候选者对都验证过了,再从中挑一个最优的进行提名。
我习惯把这种方式叫做「先看全貌,再做决定」。
流程是这样的:
- 发起方发送所有候选者的STUN绑定请求(Binding Request)
- 等所有响应都回来了,候选者对排序完成
- 从排序列表顶部选一个,发送带提名标志的请求
- 接收方确认后,ICE完成
我的经验:常规提名在P2P场景下表现很好。但如果你在用TURN中继,常规提名可能会让连接建立时间翻倍。我曾经在一个项目中,就因为用了常规提名,用户等了3秒才听到对方声音——后来改成激进提名,1秒内就通了。
激进提名(Aggressive Nomination)
激进提名就激进多了。它不等所有检查完成,只要发现一个可用的候选者对,立马提名。
说白了就是「先上车,再补票」。
流程是这样的:
- 发起方发送STUN绑定请求,同时带上提名标志
- 如果收到成功响应,立即提名这个候选者对
- 如果后续发现更好的候选者对,可以重新提名
注意:激进提名可能导致「提名抖动」——刚提名了一个,又发现更好的,又提名另一个。媒体流可能会因此短暂中断。我遇到过最夸张的情况,一次通话中提名切换了7次。
提名与ICE完成的关系
这里有个容易混淆的点:提名完成不等于ICE完成。
ICE完成需要满足两个条件:
- 至少有一个候选者对完成了提名
- 所有候选者对都已经检查完毕(或超时)
换句话说:
- 提名完成 → 可以开始发媒体了
- ICE完成 → 所有候选者都检查过了,不会再变了
我见过不少开发者把这两个混为一谈。其实你想想看,激进提名下,提名完成得很早,但ICE可能还没完成——因为还有别的候选者在排队检查呢。
关键区别:提名完成是「能用」,ICE完成是「稳定」。
提名冲突处理
提名冲突,说白了就是两边同时提名了不同的候选者对。
举个例子:
- Alice提名了候选者对A
- Bob提名了候选者对B
- A和B不是同一个候选者对
这时候怎么办?
RFC里规定了一套规则:
- 如果收到对方的提名请求,且自己还没提名,就接受对方的提名
- 如果双方都提名了,比较提名候选者的优先级,选优先级高的
- 如果优先级一样,比较双方的角色(控制方 vs 被控制方),控制方说了算
避坑指南:我曾经在一个跨平台项目中,Android端用激进提名,iOS端用常规提名,结果两边提名冲突了。Android端提名了一个低延迟的P2P候选者,iOS端提名了一个稳定的TURN候选者。最后因为角色判定问题,两边各执一词,ICE状态卡住了。解决方案是统一两端的提名策略。
提名流程的SVG图解
下面这张图展示了常规提名和激进提名的完整流程对比:
提名相关的STUN消息格式
提名是通过STUN消息中的标志位来传递的。具体来说,是在STUN绑定请求中设置一个特定的属性。
// STUN消息中的提名标志
// ICE-CONTROLLED 或 ICE-CONTROLLING 属性中携带提名信息
// 常规提名:先发不带提名标志的请求,再发带提名标志的请求
// 激进提名:直接在请求中设置提名标志
// 伪代码示例
if (nomination_mode == REGULAR) {
// 先做普通检查
send_binding_request(candidate_pair, NOMINATION_FLAG_NONE);
// 等所有检查完成,再提名
send_binding_request(best_pair, NOMINATION_FLAG_SET);
} else if (nomination_mode == AGGRESSIVE) {
// 直接带提名标志
send_binding_request(candidate_pair, NOMINATION_FLAG_SET);
}
实际项目中的选择建议:
- 如果用户对连接速度敏感(比如直播、实时通话),用激进提名
- 如果用户对稳定性敏感(比如视频会议、远程医疗),用常规提名
- 如果两端设备性能差异大,建议统一提名策略,避免冲突
提名状态机
ICE提名涉及几个关键状态:
- Waiting:等待检查结果,还没提名
- InProgress:提名请求已发送,等待响应
- Succeeded:提名成功,候选者对可用
- Failed:提名失败,需要重试或换候选者
我习惯在代码里用状态机来管理提名流程,这样不容易出错。
我的一个小技巧:在调试ICE提名问题时,我会在日志里打印每个候选者对的状态变化。这样一旦出现提名冲突或卡住,一眼就能看出问题出在哪一步。
总结
ICE提名机制,说白了就是「选路」的最后一步。常规提名稳但慢,激进提名快但可能抖。提名完成不等于ICE完成,这个区别一定要记住。提名冲突时,靠优先级和角色来裁决。
实际项目中,我建议根据场景灵活选择提名策略。没有银弹,只有最适合的。
公众号:蓝海资料掘金营,微信deep3321