1. 问题现象与排查思路拆解1.1 这个问题的典型表现是什么如果你正在用 FreeSWITCH 做 WebRTC 相关的语音或视频通话大概率遇到过这样一种情况SIP 信令层面看起来一切正常INVITE 发出去了100 Trying、180 Ringing 也都回来了但就是迟迟听不到声音或者通话建立的时间明显偏长用户体感上就是“拨出去之后要等好几秒才能听到对方说话”。抓包一看SDP 里的 ICE candidate 收集过程拖了很长时间尤其是 RTP 地址的获取有时候要等 3 到 5 秒甚至更久才完成。这个问题的核心并不在 FreeSWITCH 本身的信令处理能力上而是出在 ICEInteractive Connectivity Establishment候选地址收集这个环节。FreeSWITCH 在作为 WebRTC 网关时需要为每一路通话收集本地的 ICE candidate包括 host candidate、srflx candidate 和 relay candidate。其中 host candidate 是本地网卡地址收集很快srflx candidate 需要通过 STUN 服务器获取公网映射地址relay candidate 则需要通过 TURN 服务器分配中继地址。问题往往就出在 srflx 和 relay 这两个环节上。我见过不少部署场景FreeSWITCH 配置了 STUN 服务器但 STUN 服务器响应慢或者网络不通导致 ICE 收集超时。也有的场景是 TURN 服务器配置了但实际不可用FreeSWITCH 在等待 TURN 分配超时后才放弃白白浪费了好几秒。还有一种情况是 FreeSWITCH 的 ICE 候选地址收集策略本身没有优化默认行为是等待所有候选地址收集完成才发送 SDP这就把最慢的那个候选地址的收集时间变成了整个通话建立时间的瓶颈。1.2 为什么 ICE 收集会拖慢 RTP 地址获取要理解这个问题得先搞清楚 FreeSWITCH 在 WebRTC 通话建立过程中到底做了什么。当一路 WebRTC 通话进来时FreeSWITCH 需要生成一个 SDP answer里面包含它这一侧的 ICE candidate 列表。对端拿到这个列表后会逐一尝试连接直到找到一条可用的路径。问题在于FreeSWITCH 默认会等所有 ICE candidate 都收集完毕才发送 SDP answer而不是收集到一个就发一个。这个设计在理想网络环境下没问题因为所有候选地址都能在几百毫秒内收集完。但在实际生产环境中STUN 服务器可能因为网络抖动响应慢TURN 服务器可能因为负载高分配慢甚至有些候选地址的收集会直接超时。FreeSWITCH 的 ICE 收集有一个默认的超时时间通常是几秒级别在这段时间内它会一直等直到超时或者所有候选地址都收集完。更麻烦的是RTP 地址的获取和 ICE 候选地址的收集是绑定的。FreeSWITCH 需要从收集到的候选地址中选择一个作为 RTP 的传输地址如果候选地址收集慢RTP 地址的确定就慢媒体流自然也就建立不起来。用户感知到的就是“通了但没声音”或者“要等很久才有声音”。1.3 排查这个问题的基本思路排查这个问题我一般会按以下顺序来走。第一步确认现象在 FreeSWITCH 的日志里搜索 ICE 相关的关键字看看候选地址收集花了多长时间。第二步抓包分析在 FreeSWITCH 服务器上抓 STUN/TURN 的包看看请求发出去了没有响应回来了没有响应时间是多少。第三步检查配置看看 STUN 和 TURN 服务器的配置是否正确网络是否可达。第四步调整策略根据排查结果要么修复 STUN/TURN 服务器的问题要么调整 FreeSWITCH 的 ICE 收集策略让它在候选地址收集不完整时也能尽快发送 SDP。这里有个经验之谈很多人在遇到这个问题时第一反应是去调大 FreeSWITCH 的各种超时参数觉得等久一点就能收集到更多候选地址。但实际上调大超时只会让问题更严重因为用户等待的时间更长了。正确的做法应该是反过来缩短 ICE 收集的超时时间让 FreeSWITCH 尽快用已有的候选地址建立连接同时并行去修复 STUN/TURN 服务器的问题。提示在排查 ICE 问题时FreeSWITCH 的日志级别建议调到 debug这样可以看到 ICE 收集的详细过程包括每个候选地址的收集时间和状态。2. FreeSWITCH ICE 收集机制与关键参数解析2.1 ICE 候选地址的类型与收集顺序FreeSWITCH 在 WebRTC 场景下会收集三种类型的 ICE candidate。第一种是 host candidate也就是 FreeSWITCH 服务器本机的网卡地址包括内网 IP 和可能的公网 IP。这种候选地址收集最快基本上是瞬时的因为它不需要任何外部服务。第二种是 srflx candidate也就是服务器反射地址FreeSWITCH 需要向 STUN 服务器发送一个 Binding RequestSTUN 服务器返回它在公网侧看到的源地址。这种候选地址的收集时间取决于 STUN 服务器的响应速度。第三种是 relay candidate也就是中继地址FreeSWITCH 需要向 TURN 服务器申请一个中继端口TURN 服务器分配成功后返回中继地址和端口。这种候选地址的收集时间取决于 TURN 服务器的分配速度。收集顺序上FreeSWITCH 通常会先收集 host candidate然后并行去收集 srflx 和 relay candidate。但最终发送 SDP 的时机是等所有候选地址都收集完或者超时。这就意味着如果 srflx 或 relay 的收集很慢整个 SDP 的发送就会被拖慢。这里有个细节值得注意FreeSWITCH 的 ICE 收集并不是每次通话都重新收集所有候选地址。对于 host candidate它会在启动时缓存本机的网卡地址对于 srflx 和 relay candidate它会在每次通话时重新向 STUN/TURN 服务器请求。所以如果你发现每次通话都有延迟那问题大概率出在 srflx 或 relay 的收集上。2.2 影响 ICE 收集速度的关键参数FreeSWITCH 的 ICE 收集行为受多个参数控制这些参数分布在不同的配置文件和模块中。我整理了一个表格把最关键的几个参数列出来方便你对照检查。参数名称所在位置默认值作用说明ice-timeoutswitch.conf.xml5000msICE 收集的总超时时间超过这个时间就不再等待stun-timeoutswitch.conf.xml3000msSTUN 请求的超时时间turn-timeoutswitch.conf.xml5000msTURN 分配的超时时间rtp-timeout-secsofia profile300RTP 超时时间影响媒体流的中断检测ice-optionssofia profile空ICE 选项可以控制是否启用某些候选地址类型这些参数里ice-timeout是最关键的。默认 5000ms 意味着 FreeSWITCH 最多等 5 秒来收集候选地址。如果你的 STUN 或 TURN 服务器响应慢这 5 秒就会被白白浪费掉。我一般会把这个值调到 1000ms 到 2000ms 之间具体取决于你的网络环境和 STUN/TURN 服务器的响应速度。stun-timeout和turn-timeout也需要相应调整。如果 STUN 服务器在 3000ms 内没有响应FreeSWITCH 就会放弃 srflx candidate 的收集。但如果你的 STUN 服务器本身响应很快这个值可以调小到 500ms 甚至更短。TURN 的分配通常比 STUN 慢一些因为涉及到端口分配和权限验证所以turn-timeout可以适当保留在 1000ms 到 2000ms 之间。2.3 候选地址收集与 SDP 发送的时序关系理解 ICE 收集和 SDP 发送的时序关系是解决这个问题的关键。FreeSWITCH 的处理流程大致是这样的收到 INVITE 后解析 SDP提取对端的 ICE candidate 列表然后开始收集本地的 ICE candidate等本地候选地址收集完成后生成 SDP answer 并发送给对端对端收到 SDP answer 后开始 ICE 连通性检查找到可用的路径后RTP 流才开始传输。这个流程里最耗时的就是本地候选地址收集这一步。如果 STUN 服务器响应慢srflx candidate 的收集就会慢如果 TURN 服务器分配慢relay candidate 的收集就会慢。而 FreeSWITCH 默认是等所有候选地址都收集完才发送 SDP answer所以最慢的那个候选地址的收集时间就成了整个通话建立时间的瓶颈。有一种优化思路是让 FreeSWITCH 在收集到 host candidate 后就立即发送 SDP answer不等 srflx 和 relay candidate。这样对端可以先用 host candidate 尝试连接如果连不通再等后续的候选地址。但这种做法需要修改 FreeSWITCH 的源码或者使用特定的配置选项并不是所有版本都支持。在不能修改源码的情况下更实际的做法是优化 STUN/TURN 服务器的响应速度同时缩短 ICE 收集的超时时间。注意缩短 ICE 超时时间虽然能加快 SDP 发送但也可能导致某些候选地址收集不完整。如果你的网络环境比较复杂需要 srflx 或 relay candidate 才能连通那就要在超时时间和候选地址完整性之间做一个权衡。3. 实操优化方案与配置调整3.1 第一步确认 STUN/TURN 服务器的可用性与响应速度在调整 FreeSWITCH 配置之前我强烈建议先确认 STUN/TURN 服务器本身是否正常。很多时候问题不在 FreeSWITCH 这边而是 STUN/TURN 服务器挂了或者响应慢。你可以用一些简单的工具来测试 STUN 服务器的响应速度比如在 Linux 上用stunclient命令或者在浏览器里用一些在线的 STUN 测试工具。测试的时候重点关注两个指标一是响应时间正常的 STUN 服务器应该在 50ms 到 200ms 内返回响应二是成功率连续测试 10 次看看有多少次能成功返回。如果响应时间超过 500ms 或者成功率低于 90%那 STUN 服务器本身就有问题需要先解决服务器端的问题。TURN 服务器的测试稍微复杂一些因为它涉及到分配中继地址。你可以用turnutils_uclient这个工具来测试 TURN 服务器的分配速度和连通性。测试的时候注意观察分配耗时正常的 TURN 服务器应该在 200ms 到 500ms 内完成分配。如果超过 1 秒那 TURN 服务器的负载可能过高或者网络延迟太大。这里有个实操心得如果你的 STUN/TURN 服务器和 FreeSWITCH 部署在同一机房响应时间应该非常短通常在 10ms 以内。如果响应时间超过 100ms那说明网络路径有问题可能是跨机房或者跨地域了。这种情况下建议把 STUN/TURN 服务器部署到离 FreeSWITCH 更近的位置。3.2 第二步调整 FreeSWITCH 的 ICE 超时参数确认 STUN/TURN 服务器正常后接下来就是调整 FreeSWITCH 的 ICE 超时参数。这些参数主要在switch.conf.xml和 sofia profile 的配置文件中。我一般会按以下步骤来调整。首先打开switch.conf.xml找到 ICE 相关的配置段。如果没有显式配置FreeSWITCH 会使用默认值。我建议显式配置以下参数param nameice-timeout value1500/ param namestun-timeout value500/ param nameturn-timeout value1000/这里把ice-timeout设为 1500ms意味着 FreeSWITCH 最多等 1.5 秒来收集候选地址。stun-timeout设为 500ms因为 STUN 请求通常很快500ms 足够。turn-timeout设为 1000ms给 TURN 分配留出足够的时间但也不至于太长。然后在 sofia profile 的配置文件中检查ice-options参数。这个参数控制 ICE 的行为可以设置的值包括aggressive、regular等。在 WebRTC 场景下我一般建议设置为aggressive这样 FreeSWITCH 会更积极地发送候选地址而不是等所有候选地址都收集完。param nameice-options valueaggressive/调整完这些参数后重启 FreeSWITCH 或者重新加载配置然后再次测试通话建立时间。你应该能看到明显的改善通话建立时间从原来的 3 到 5 秒缩短到 1 秒以内。3.3 第三步优化网络路径与 STUN/TURN 部署位置如果调整参数后效果仍然不理想那就要考虑网络路径的问题了。STUN/TURN 服务器和 FreeSWITCH 之间的网络延迟直接影响候选地址的收集速度。我遇到过不少案例STUN/TURN 服务器部署在公网而 FreeSWITCH 部署在内网两者之间的网络延迟高达 100ms 以上导致每次 ICE 收集都要多花几百毫秒。优化的思路很简单把 STUN/TURN 服务器部署到离 FreeSWITCH 尽可能近的位置。如果 FreeSWITCH 在内网那 STUN/TURN 也应该部署在内网或者至少在同一机房。如果 FreeSWITCH 在云上那 STUN/TURN 也应该部署在同一云服务商的同一区域。另外还要检查 FreeSWITCH 服务器的网卡配置。如果服务器有多张网卡FreeSWITCH 可能会在所有网卡上都收集 host candidate这会增加候选地址的数量但也会增加收集时间。我一般建议在 sofia profile 中显式指定 RTP 使用的网卡避免在不必要的网卡上收集候选地址。param namertp-ip value192.168.1.100/ param namesip-ip value192.168.1.100/这样 FreeSWITCH 就只会在这个指定的 IP 上收集 host candidate减少了候选地址的数量也缩短了收集时间。3.4 第四步验证优化效果与持续监控调整完配置后一定要验证优化效果。我一般会用以下几种方法来验证。第一种是看 FreeSWITCH 的日志搜索 ICE 相关的关键字看看候选地址收集花了多长时间。第二种是抓包分析在 FreeSWITCH 服务器上抓 STUN/TURN 的包看看请求和响应的时间差。第三种是实际通话测试用 WebRTC 客户端拨打电话记录从拨出到听到声音的时间。验证的时候要注意优化效果可能会因为网络环境的不同而有差异。在测试环境下效果很好不代表在生产环境下也一定好。所以建议在生产环境上线前先在预发布环境做充分的测试。持续监控也很重要。我一般会在 FreeSWITCH 上配置一些监控指标比如 ICE 收集的平均耗时、STUN/TURN 请求的成功率、通话建立的平均时间等。这些指标可以帮助你及时发现问题的回归避免优化效果随着时间的推移而退化。监控指标正常范围异常阈值排查方向ICE 收集平均耗时 500ms 1000ms检查 STUN/TURN 响应速度STUN 请求成功率 99% 95%检查 STUN 服务器状态TURN 分配成功率 99% 95%检查 TURN 服务器负载通话建立平均时间 1s 2s综合排查 ICE 和网络4. 常见问题与排查技巧实录4.1 为什么调整了超时参数但效果不明显这是我最常被问到的问题之一。很多人按照网上的教程调整了ice-timeout和stun-timeout但发现通话建立时间并没有明显改善。这种情况通常有几个原因。第一个原因是配置没有生效可能是改错了文件或者改完之后没有重启 FreeSWITCH。第二个原因是 STUN/TURN 服务器本身响应就很慢即使超时时间调小了FreeSWITCH 在超时前也收集不到候选地址结果还是一样的。第三个原因是网络路径的问题STUN/TURN 服务器和 FreeSWITCH 之间的延迟太高导致请求和响应的时间本身就超过了超时时间。排查这个问题的时候我一般会先确认配置是否生效。可以在 FreeSWITCH 的命令行界面里用sofia status profile internal命令查看当前生效的配置。然后抓包看看 STUN/TURN 的请求和响应时间如果响应时间本身就超过了超时时间那调小超时时间也没用需要先解决服务器端的问题。还有一个容易被忽略的点FreeSWITCH 的 ICE 收集是异步的多个候选地址的收集是并行进行的。如果你只调小了ice-timeout但没有调小stun-timeout和turn-timeout那 STUN 和 TURN 的收集仍然会等到各自的超时时间才放弃整体收集时间并没有缩短。所以这几个参数要一起调整才能看到明显的效果。4.2 STUN 服务器响应正常但 srflx candidate 仍然收集不到有时候你会遇到一种奇怪的情况用工具测试 STUN 服务器响应很快成功率也很高但 FreeSWITCH 就是收集不到 srflx candidate。这种情况通常和 FreeSWITCH 的 STUN 配置有关。FreeSWITCH 需要显式配置 STUN 服务器的地址如果没有配置或者配置错误它就不会去请求 STUN 服务器。检查 FreeSWITCH 的 STUN 配置主要看switch.conf.xml里有没有stun-server这个参数。如果没有需要手动加上param namestun-server valuestun.example.com:3478/另外还要确认 FreeSWITCH 的 sofia profile 里有没有启用 ICE。有些配置模板默认是不启用 ICE 的需要显式打开param nameice-enabled valuetrue/还有一个可能的原因是防火墙或安全组规则限制了 FreeSWITCH 到 STUN 服务器的 UDP 流量。STUN 请求默认走 UDP 3478 端口如果这个端口被防火墙拦截了FreeSWITCH 就收不到 STUN 响应。这种情况下抓包会看到请求发出去了但没有响应或者响应被拦截了。4.3 TURN 服务器分配慢导致 relay candidate 收集超时TURN 服务器的分配速度受多个因素影响包括服务器负载、网络延迟、认证方式等。如果 TURN 服务器分配慢relay candidate 的收集就会超时进而拖慢整个 ICE 收集过程。我遇到过几种典型的 TURN 分配慢的场景。第一种是 TURN 服务器负载过高。当并发通话数很多时TURN 服务器需要为每一路通话分配独立的中继端口如果端口资源不足或者 CPU 负载过高分配速度就会明显下降。这种情况下要么扩容 TURN 服务器要么限制并发通话数。第二种是 TURN 认证方式导致的延迟。有些 TURN 服务器配置了复杂的认证机制比如每次分配都需要查询数据库或者调用外部接口这会增加分配时间。如果可能的话尽量使用简单的认证方式或者把认证信息缓存起来。第三种是网络延迟。如果 TURN 服务器和 FreeSWITCH 之间的网络延迟很高TURN 分配的请求和响应时间就会增加。这种情况下把 TURN 服务器部署到离 FreeSWITCH 更近的位置是最有效的解决办法。提示如果你的场景不需要 relay candidate比如 FreeSWITCH 和对端都在同一个内网那可以考虑禁用 TURN只保留 host 和 srflx candidate。这样可以避免 TURN 分配带来的延迟。4.4 常见问题速查表为了方便你快速定位问题我整理了一个常见问题速查表列出了症状、可能原因和排查方法。症状可能原因排查方法通话建立慢但最终能通ICE 收集超时时间过长检查 ice-timeout 配置抓包看 STUN/TURN 响应时间通话建立慢且经常失败STUN/TURN 服务器不可用用工具测试 STUN/TURN 服务器连通性srflx candidate 收集不到STUN 配置缺失或防火墙拦截检查 stun-server 配置抓包看 UDP 3478 流量relay candidate 收集超时TURN 服务器负载高或网络延迟大检查 TURN 服务器负载测试网络延迟调整参数后无改善配置未生效或多个参数未一起调整确认配置生效同时调整 ice/stun/turn 超时部分通话正常部分通话慢网络抖动或 STUN/TURN 服务器间歇性故障持续监控 STUN/TURN 响应时间查看服务器日志4.5 几个容易被忽略的实操细节在实际操作中有几个细节很容易被忽略但往往对结果影响很大。第一个细节是 FreeSWITCH 的版本差异。不同版本的 FreeSWITCH 在 ICE 处理上可能有差异有些版本对 ICE 超时的处理逻辑不一样。我建议在调整配置前先确认你的 FreeSWITCH 版本然后查阅对应版本的文档。第二个细节是 SDP 中的 candidate 数量。如果 FreeSWITCH 收集到的 candidate 太多SDP 会变得很大传输和处理的时间也会增加。我一般会限制 candidate 的数量只保留必要的几种类型。可以在 sofia profile 中配置ice-candidate-count参数来控制。第三个细节是日志级别。在排查 ICE 问题时把日志级别调到 debug 可以看到详细的收集过程但 debug 日志量很大可能会影响 FreeSWITCH 的性能。所以建议只在排查问题时临时开启问题解决后调回正常级别。第四个细节是时间同步。FreeSWITCH 和 STUN/TURN 服务器的时间要同步否则在分析日志时会出现时间戳对不上的情况增加排查难度。建议所有服务器都配置 NTP 时间同步。5. 进阶优化与架构层面的思考5.1 使用 ICE Lite 模式减少候选地址收集如果你的 FreeSWITCH 部署在网络环境比较可控的场景下比如所有客户端都在同一个内网或者 FreeSWITCH 有固定的公网地址那可以考虑使用 ICE Lite 模式。ICE Lite 模式下FreeSWITCH 只收集 host candidate不收集 srflx 和 relay candidate这样可以大幅缩短 ICE 收集时间。ICE Lite 模式的配置方法是在 sofia profile 中设置ice-options为liteparam nameice-options valuelite/但要注意ICE Lite 模式要求 FreeSWITCH 有一个固定的、对端可以直接访问的地址。如果 FreeSWITCH 在 NAT 后面或者对端无法直接访问 FreeSWITCH 的 host candidate那 ICE Lite 模式就行不通。所以这个模式适合特定的部署场景不是万能的。5.2 部署本地 STUN/TURN 服务减少网络依赖如果你的 STUN/TURN 服务器部署在公网每次 ICE 收集都要走公网那网络延迟和抖动就会直接影响收集速度。一个有效的优化方案是在 FreeSWITCH 本地或者同一内网部署 STUN/TURN 服务。这样 STUN/TURN 请求走内网延迟可以降到 1ms 以内收集速度会大幅提升。本地部署 STUN/TURN 服务的选择很多有开源的实现也有商业产品。选择的时候重点关注性能、稳定性和可维护性。我一般建议选择轻量级的实现因为 FreeSWITCH 对 STUN/TURN 的需求主要是功能性的不需要太复杂的功能。部署的时候要注意STUN/TURN 服务要和 FreeSWITCH 在同一内网并且网络质量要好。如果内网本身就有丢包或者延迟那本地部署也解决不了问题。另外本地部署的 STUN/TURN 服务也要做高可用避免单点故障。5.3 监控与告警体系的搭建优化不是一次性的工作而是一个持续的过程。搭建一套监控与告警体系可以帮助你及时发现 ICE 收集时间的退化避免问题积累到影响用户体验才被发现。我一般会监控以下几个指标。第一个指标是 ICE 收集的平均耗时。这个指标可以直接从 FreeSWITCH 的日志中提取或者通过自定义的脚本统计。正常的平均耗时应该在 500ms 以内如果超过 1 秒就需要关注了。第二个指标是 STUN/TURN 请求的成功率和响应时间。这个指标可以通过在 FreeSWITCH 服务器上定期发送测试请求来获取也可以从 STUN/TURN 服务器的日志中统计。第三个指标是通话建立的平均时间。这个指标可以从 FreeSWITCH 的 CDR 中提取反映了用户的实际体验。如果通话建立时间明显增加那就要排查 ICE 收集是否变慢了。告警的阈值要根据你的业务需求来设定。我一般会把 ICE 收集平均耗时超过 1 秒、STUN/TURN 成功率低于 95%、通话建立平均时间超过 2 秒设为告警条件。告警方式可以是邮件、短信或者即时消息根据团队的运维习惯来选择。5.4 架构层面的优化思路如果单靠调整参数和优化 STUN/TURN 服务器还是不能满足要求那就要考虑架构层面的优化了。一个思路是把 ICE 收集的工作从 FreeSWITCH 中剥离出来用独立的服务来处理。这个服务可以预先收集好候选地址缓存起来FreeSWITCH 需要的时候直接取用避免每次通话都重新收集。另一个思路是使用边缘节点来分担 ICE 收集的压力。在离用户更近的位置部署边缘节点由边缘节点负责 ICE 收集和媒体转发FreeSWITCH 只负责信令处理。这样可以减少 FreeSWITCH 的负担也可以缩短 ICE 收集的时间。还有一个思路是优化 SDP 的生成和传输流程。比如在收集到 host candidate 后就立即发送 SDP后续的候选地址通过 SIP UPDATE 或者 re-INVITE 来补充。这样对端可以更早开始 ICE 连通性检查缩短整体通话建立时间。但这种做法需要 FreeSWITCH 和对端都支持相应的 SIP 扩展兼容性需要验证。我在实际项目中的体会是ICE 收集时间的优化没有一劳永逸的方案需要根据具体的网络环境、业务需求和 FreeSWITCH 的版本特点来综合施策。参数调整是最简单也最直接的手段但往往只能解决一部分问题。真正要彻底解决还是要从网络架构和 STUN/TURN 服务器的部署上下功夫。另外监控和告警体系的建设也很重要它可以帮助你及时发现问题的回归避免优化效果随着时间的推移而退化。最后再分享一个小技巧在调整任何参数之前一定要先做好备份并且记录下调整前的基线数据这样调整后才能准确评估优化效果。