27、ICE候选者优先级算法:候选者对优先级计算、类型优先级与本地优先级、RFC 5245算法

ICE 候选者优先级,说白了就是给每个候选者排个座次。谁排在前面,谁就有机会先被用来建立连接。我刚开始接触 WebRTC 时,觉得这步就是个排序嘛,有啥难的?后来踩了坑才发现,这里面的门道还真不少。

为什么需要优先级?

你想想看,一个端上可能收集到几十个候选者。有 host 的、有 srflx 的、有 relay 的。如果同时拿这些候选者去连对端,网络带宽和 CPU 都扛不住。所以 ICE 协议规定:先排序,再按顺序尝试连接。

优先级高的候选者,会被优先用来做连通性检查。这就像你手里有一把钥匙,肯定先试最可能打开门的那一把。

RFC 5245 中的优先级算法

RFC 5245 里给出了一个标准公式。我每次讲到这里,都会提醒学员:这个公式不是随便写的,它背后有设计考量。

priority = (2^24) * type_preference +
           (2^8) * local_preference +
           (2^0) * (256 - component_id)

嗯,这里要注意:公式里的三个部分,权重完全不同。

  • type_preference:类型优先级,占最高 8 位
  • local_preference:本地优先级,占中间 8 位
  • component_id:组件 ID,占最低 8 位

为什么 type_preference 的权重最高?因为类型决定了候选者的可靠性。host 候选者走本地网络,延迟最低;relay 候选者走中继,延迟最高。这个排序逻辑很直观。

类型优先级(Type Preference)

RFC 5245 推荐了默认的类型优先级值。我个人习惯用这套默认值,除非有特殊需求才去改它。

候选者类型 推荐优先级 说明
host 126 本地接口,延迟最低
srflx 100 NAT 映射,需要穿透
relay 0 中继转发,延迟最高
peer-reflexive 110 连通性检查中发现的

我在项目中遇到过一个问题:某个客户的内网环境特别复杂,host 候选者反而连不上。当时我临时调整了 type_preference,把 srflx 的优先级调高,才解决了问题。所以默认值不是铁律,要根据实际场景灵活调整。

本地优先级(Local Preference)

本地优先级是用来区分同一类型下的不同候选者的。比如你有两个 host 候选者,一个走 WiFi,一个走以太网。你可以通过本地优先级告诉 ICE:优先用哪个。

RFC 5245 没有规定本地优先级的具体算法,留给了实现者自己决定。我一般这样处理:

  • 以太网接口:高优先级(比如 65535)
  • WiFi 接口:中等优先级(比如 50000)
  • 移动网络接口:低优先级(比如 30000)

为什么这么分?因为以太网通常比 WiFi 稳定,WiFi 又比移动网络稳定。你想想看,视频通话时突然从 WiFi 切到 4G,那体验能好吗?

组件 ID(Component ID)

组件 ID 在公式里是 (256 - component_id)。RTP 的 component_id 是 1,RTCP 是 2。所以 RTP 的优先级天然比 RTCP 高。

这个设计很合理。RTP 承载音视频数据,RTCP 只是控制信息。如果带宽不够,肯定优先保证 RTP 的连通性。RTCP 可以等一等,甚至丢几个包也没关系。

实际计算示例

我们来看一个具体的例子。假设有一个 host 候选者,本地优先级设为 65535,组件 ID 为 1:

priority = (2^24) * 126 +
           (2^8) * 65535 +
           (2^0) * (256 - 1)

= 16777216 * 126 +
  256 * 65535 +
  255

= 2113929216 +
  16776960 +
  255

= 2130706431

这个值很大对吧?别担心,ICE 协议里 priority 字段是 64 位的,完全装得下。

优先级与候选者配对

优先级不仅影响单个候选者的排序,还影响候选者配对的顺序。ICE 会把本地候选者和远端候选者两两配对,然后按优先级排序。优先级高的配对,会先做连通性检查。

我曾经踩过一个坑:两个端都收集到了很多候选者,配对数量爆炸,导致连通性检查超时。后来我限制了每个类型的候选者数量,才解决了问题。嗯,这里要提醒大家:候选者不是越多越好,够用就行。

SVG 流程图:优先级计算与配对流程

ICE 候选者优先级计算与配对流程 收集候选者 计算优先级 按优先级排序 类型优先级(Type Preference) host = 126 | srflx = 100 | peer-reflexive = 110 | relay = 0 本地优先级(Local Preference):实现自定义,通常按网络接口类型区分 组件 ID:RTP = 1,RTCP = 2(RTP 优先级更高) 候选者配对与连通性检查 1. 本地候选者列表 × 远端候选者列表 = 所有配对 2. 按配对中最高优先级排序(取本地和远端中较高的那个) 3. 优先级高的配对先做连通性检查(STUN 请求) ⚠ 注意:候选者数量过多会导致配对爆炸,建议限制每类候选者数量

避坑指南

我曾经踩过的坑:

  • 候选者数量过多导致配对爆炸,连通性检查超时
  • 本地优先级设置不合理,导致 WiFi 和以太网切换频繁
  • 没有考虑 peer-reflexive 候选者,导致连接建立延迟

我的建议:

  • 每个类型最多保留 5 个候选者,够用就行
  • 本地优先级按网络接口稳定性排序,不要只看带宽
  • 如果遇到连接问题,先检查候选者优先级是否合理

总结

ICE 候选者优先级算法,说白了就是一套排序规则。类型优先级决定大类,本地优先级决定小类,组件 ID 决定内部顺序。这三者组合起来,就能给每个候选者一个唯一的优先级值。

我在实际项目中,很少去改默认的类型优先级。但本地优先级经常会根据网络环境做调整。比如在移动端,我会把 WiFi 的优先级设得比 4G 高,但也会留一个降级策略,防止 WiFi 信号不好时切不过去。

嗯,优先级算法就讲到这里。记住一句话:优先级不是越高越好,而是要符合你的实际网络场景。


公众号:蓝海资料掘金营,微信deep3321