WebRTC 中 ICE 流程优化:SRS 轻量级部署与 NAT 类型检测实战

发布于:2025-06-12 ⋅ 阅读:(81) ⋅ 点赞:(0)

1 ICE基本知识

1.1 ICE简介

ICE全称Interactive Connectivity Establishment——交互式连通建设形式。
在这里插入图片描述
Interactive Connectivity Establishment(ICE,交互连接建立)是一套用于在异构网络环境中,实现终端(代理,Agent)间媒体流(典型如音视频数据,基于 UDP 传输为基础场景)连通性的关键框架。其核心逻辑围绕Candidate Transport Address(候选传输地址)展开,这些地址本质是**「IP 地址 + 端口 + 传输协议(ICE 标准场景默认 UDP)」的三元组集合,**用于描述终端可达的网络定位,具体涵盖三类典型场景:

1. 直连传输地址(Host Candidate)
指终端直接连接的网络接口所绑定的传输地址,若终端处于公网环境(如公网服务器、直接分配公网 IP 的终端),则表现为公网 IP 直连模式。此时,终端可通过标准套接字(Socket)直接建立 TCP/UDP 连接,完成数据收发。在 OSI 模型中,属于网络层(IP 寻址)与传输层(端口复用、连接建立)的直接映射,是最简化的端到端通信场景,无额外网络地址转换开销。

2. NAT 转换传输地址(Server-Reflexive Candidate)
因 IPv4 地址枯竭问题,NAT(Network Address Translation,网络地址转换)作为过渡技术广泛部署:终端(私网环境)发起对外连接时,NAT 设备会将私网 IP: 端口 映射为公网转换地址(公网 IP: 端口 ),实现私网终端与公网的通信。然而,NAT 技术也成为 IPv6 普及的阻碍因素之一:现有网络中大量设备依赖 NAT 稳定工作,形成 “技术惯性”,降低了网络运营商与企业升级 IPv6 网络的动力(IPv6 理论上可通过海量地址解决寻址问题,无需 NAT 过渡),导致 IPv6 部署推进存在现实阻力。

从协议交互看,NAT 会改变 IP 数据包的源地址(私网→公网转换),且不同 NAT 类型(如完全圆锥型、端口限制型等)对 “外部主动发起连接” 的处理逻辑差异极大,直接影响 ICE 候选地址的连通性检测难度。

3. 中继传输地址(Relayed Candidate)
由 TURN(Traversal Using Relays around NAT,中继穿越 NAT)服务器分配的传输地址,属于中继模式。当终端受限于严格 NAT 类型(如对称型 NAT,无法通过 STUN 完成地址穿透)或防火墙策略时,ICE 会启用 TURN 中继:终端先与 TURN 服务器建立连接,媒体数据通过 TURN 服务器 “中转” 转发。

该模式的代价体现在:

传输延迟:数据需经过 “终端→TURN 服务器→对端终端” 的额外路径,增加网络时延(典型场景下,中继会使 RTT 至少翻倍);
服务器资源消耗:TURN 服务器需承担数据转发、地址映射维护、流量中继等负载,对带宽、计算资源要求高;
协议复杂度:需额外实现 TURN 协议交互(如分配中继地址、数据中继转发逻辑),增加终端与服务端的开发成本。

1.2 NAT的类型

为啥 ICE 交互难?因为 NAT 搞事儿
很多设备(像家里电脑、手机)在“内网”里,要和外网设备通信,得靠 NAT(网络地址转换) 把内网地址换成公网地址。但 NAT 有不同“脾气”,有的限制多,导致设备间直接通信(ICE 要做的事儿)变困难,所以得用 STUN、TURN 这些方法“克服”它 。

NAT 的常见“脾气”(类型)
把 NAT 简单分成 对称型 NAT圆锥形 NAT ,圆锥形还分三种“亚型”,它们对设备通信的限制各不相同:

1. 完全圆锥型 NAT(“大敞开门”型 )

  • 特点:内网里一个设备(用 IP1:Port1 表示,IP 是地址,Port 是端口),不管给哪个外网设备发数据,NAT 都会给它映射成 固定的公网地址 IP2:Port2 。而且,只要外网设备给 IP2:Port2 发数据,NAT 就会转交给内网的 IP1:Port1
  • 举例:你家电脑(内网 192.168.1.2:8080 )访问过公网服务器 M,NAT 给它映射成公网 203.0.113.2:8888 。之后,哪怕另一台公网服务器 P 主动给 203.0.113.2:8888 发数据,你家电脑也能收到(不用先主动连 P )。 有些机房内网的反向代理,就类似这种“大敞开”的 NAT 。

2. IP 限制圆锥型 NAT(“认 IP 才让进”型 )

  • 特点:内网 IP1:Port1 给外网发数据时,NAT 会映射成 IP2:Port2 ,但 这个映射和“外网目标设备的 IP”绑定 。要是内网设备没主动给某个外网 IP(比如 IP3 )发过数据,那 IP3 设备给 IP2:Port2 发数据,NAT 直接“扔了”,不会转给内网设备 。
  • 举例:你家电脑先主动连了公网服务器 P(203.0.113.3 ),NAT 映射成公网 203.0.113.2:8888 ,这时 P 给 203.0.113.2:8888 发数据,电脑能收到。但要是另一台公网服务器 M(203.0.113.4 )没被电脑主动访问过,M 发数据到 203.0.113.2:8888 ,就会被 NAT 拒绝,电脑收不到。

3. Port 限制圆锥型 NAT(“认 IP + 端口 才让进”型 ,比上面更严格 )

  • 特点:内网 IP1:Port1 给外网发数据,NAT 映射的 IP2:Port2不仅和“外网目标 IP”绑定,还和“目标端口”绑定 。要是内网设备没主动给外网某个 IP3:Port3 发过数据,那 IP3:Port3IP2:Port2 发数据,NAT 也会“扔了” 。
  • 举例:你家电脑主动连公网服务器 P 的 80 端口(203.0.113.3:80 ),NAT 映射成公网 203.0.113.2:8888 。之后,只有 P 的 80 端口给 203.0.113.2:8888 发数据,电脑才能收到;要是 P 换个端口(比如 81 )发,或者其他服务器(比如 M 的 80 端口)发,NAT 都不认,电脑收不到。 可以理解成:IP 限制圆锥型是“认对方 IP 就行”,Port 限制圆锥型是“既认 IP ,也认端口” 。

4. 对称型 NAT(“每次通信都换映射”型 ,最严格 )

  • 特点:内网 IP1:Port1 给不同外网设备发数据,NAT 会 每次都换不同的公网映射地址 。比如,连外网 IP2:Port2 时,映射成 IP3:Port3 ;连另一个 IP4:Port4 时,又映射成 IP5:Port5 。而且,只有主动访问过的外网设备 + 端口,给对应映射地址发数据,内网设备才能收到 。
  • 举例:你家电脑先连公网服务器 P 的 80 端口(203.0.113.3:80 ),NAT 映射成 203.0.113.2:8888 ,此时 P 的 80 端口发数据到 203.0.113.2:8888 ,电脑能收。接着电脑连公网服务器 M 的 8080 端口(203.0.113.4:8080 ),NAT 又映射成 203.0.113.2:9999 ,M 的 8080 端口发数据到 203.0.113.2:9999 ,电脑也能收。但要是有个没访问过的服务器 S ,不管用啥端口给 203.0.113.2 发数据,电脑都收不到 。 它和 Port 限制圆锥型的区别是:Port 限制型“多次连不同设备,可能用同一个公网映射”,对称型是“每次连新设备/端口,都换公网映射” 。

克服 NAT 限制的方法:STUN、TURN
因为 NAT 有各种限制,直接通信难,所以 ICE 里用了这俩“工具”:

  • STUN:简单说,就是让设备先“问问”公网,自己被 NAT 映射成啥地址了。拿到这个公网映射地址,尝试直接和对方通信。但碰到严格的 NAT(比如对称型、Port 限制型 ),可能就“打不通” 。
  • TURN:要是 STUN 搞不定(通信不通),就走 TURN 。TURN 相当于一个“中继服务器”,双方数据都先发给 TURN 服务器,再由服务器转发给对方。虽然绕了一圈,但能保证通信,就是可能慢一点、占服务器资源 。

总结一下:ICE 交互难,主要是 NAT 各种“限制”搞的鬼。不同 NAT 对“内网设备能不能收到外网主动发的数据”,有不同规则。STUN 尝试让设备直接通信,TURN 当“Plan B”负责中继转发,一起帮着解决 NAT 带来的通信难题~

1.3 STUN经典协议

一、STUN 协议(以 RFC5389 为核心)
(一)基础定义与演进
STUN(Session Traversal Utilities for NAT ,NAT 会话遍历实用程序 )是一套网络协议,最初由 RFC3489 定义,后因局限性多,迭代出 RFC5389 作为升级版,成为解决 NAT 环境下终端互通问题的基础工具,常配合 ICE 用于实时音视频(如 WebRTC )等需要端到端通信的场景。

(二)核心功能(针对 RFC3489 局限性的改进)

  • 确定公网映射地址可用性
    RFC5389 引入更完善的“Binding Request/Response”(绑定请求/响应)机制。终端发 Binding Request 给 STUN 服务器,服务器返回包含终端经 NAT 转换后的公网映射地址等信息的响应,还能借助交互检测该地址能否被外部访问,解决了 RFC3489 无法验证公网地址 P2P 通信可用性的问题 。
  • 加密与安全认证
    支持 TLS(传输层安全协议 ),可对协议交互数据加密,防止被窃听、篡改;还引入安全认证机制,确保通信双方(终端与 STUN 服务器 )身份可信,补上 RFC3489 无加密、无可靠身份校验的短板 。
  • 协议与网络适配
    • 突破 RFC3489 仅支持 UDP 的限制,兼容 UDP、TCP、TLS 协议 ,让终端在 UDP 被限制(如部分网络环境封锁 UDP 端口 )时,能用 TCP/TLS 传输 STUN 信令,拓宽适用场景。
    • 支持 IPv6 ,适配下一代网络协议,解决 RFC3489 不兼容 IPv6 的问题,让双栈(IPv4/IPv6 )终端也能正常获取公网映射地址 。
    • 可应对对称型 NAT 穿越:通过更智能的地址探测、Binding 交互逻辑,尝试在对称型 NAT 环境下找到可用映射,一定程度解决 RFC3489 难以穿透对称型 NAT 的问题(但受对称型 NAT 本身严格限制,仍存在部分场景无法穿透情况 )。

(三)在 ICE 中的角色
ICE 依赖 STUN(RFC5389 )的 Binding 交互,完成两项关键任务:

  • 收集候选地址:终端向 STUN 服务器发 Binding Request ,获取经 NAT 转换的公网映射地址(Server - Reflexive Candidate ),丰富 ICE 候选地址池,为后续连通性检测、选路做准备 。
  • 连通性检查:ICE 会在不同候选地址对(Candidate Pair )间,用 STUN Binding 消息做“连通性探针”,验证双方能否通过该地址对收发数据,筛选出真正可用的通信路径 。
    同时,ICE 扩展了 STUN 部分属性:
    • PRIORITY:用于计算候选地址对(Candidate Pair )的优先级,ICE 依据优先级选最优通信路径,让更优(如延迟低、稳定性高 )的地址对优先被尝试 。
    • USE - CANDIDATE:ICE “提名”(Nomination )流程里用,标记某个候选地址对已通过连通性检查,可作为正式通信路径,简化后续协商逻辑 。
    • tie - breaker:当通信双方(Controlling/Controlled 角色 )对候选地址优先级判断冲突时,用该属性做“打破平局”依据,避免协商死锁 。

二、TURN 协议(RFC 5766 )
(一)基础定位
TURN 是 STUN 的“辅助兜底”协议,当 STUN 尝试端到端(P2P )穿透 NAT 失败(如遇到严格对称型 NAT、多层 NAT 嵌套 )时,TURN 作为中继(Relay )服务,让终端数据经 TURN 服务器转发实现互通,保障通信不中断 。

(二)核心功能与扩展

  • 中继转发:终端先和 TURN 服务器建立连接(可通过 STUN 流程协商中继地址 ),发送给对端的数据先到 TURN 服务器,再由服务器转发给对端;同理,对端数据也经 TURN 服务器中继到本端。即便端到端直连完全不通,也能通过中继完成通信,代价是增加传输延迟、占用服务器带宽/资源 。
  • 协议与网络适配
    • 消息格式上,除“ChannelData”(通道数据 )消息外,遵循 STUN 格式,便于和 STUN 协同工作(如复用端口、协议解析逻辑 )。
    • 支持 UDP、TCP、TLS 协议,适配 UDP 受限网络;也支持 IPv6 ,和 STUN(RFC5389 )配合,覆盖更多复杂网络环境 。
  • 地址分配:TURN 服务器会给终端分配中继地址(Relayed Candidate ),作为终端在中继模式下的“公网标识”,ICE 会把该地址纳入候选地址池,供连通性检测和选路。

三、两者关系与在 ICE 里的协同

  • 互补关系:STUN 主打“端到端直连尝试”,尽量让终端直接通信;TURN 作为“保底方案”,直连不行就中继转发。ICE 会先优先用 STUN 找直连路径,失败后自动切换到 TURN 中继,保障通信可靠性 。
  • 协议联动:TURN 消息格式(除特定消息 )复用 STUN ,端口也和 STUN 保持一致,让终端、服务器能基于一套基础逻辑,同时处理 STUN 直连探测和 TURN 中继协商,降低实现复杂度 。

简单说,STUN(RFC5389 )是解决 NAT 穿透、找直连路径的“先锋”,通过升级解决了老版本诸多局限;TURN 是“后盾”,直连失败时用中继兜底。两者配合 ICE ,让终端在复杂网络(多 NAT、协议限制等 )里,也能找到可行的音视频、数据通信路径,是实时交互应用(如 WebRTC 通话 )能跨网互通的关键支撑~

1.4 ICE基本概念

以下是对 ICE 角色、模式及候选地址的专业优化阐述,结合协议规范与典型应用场景(如 WebRTC/SRS)进行技术细节澄清与逻辑重构:

1.4.1 ICE 角色:Controlling 与 Controlled

ICE 定义了两种角色以解决候选地址优先级冲突问题,通过 角色协商机制 确保两端对通信路径达成一致:

  • Controlling 角色(控制方)

    • 定义:主动发起 Offer(如 WebRTC 客户端创建会话描述)的一方,负责主导候选地址对的优先级排序与连通性检查流程。
    • 特性:需实现 完整 ICE 流程(Full ICE Agent),即主动发送 STUN Binding Request 并接收响应,同时处理对端的 Binding Request。
    • 场景:通常为发起实时通信的终端(如浏览器客户端),需遍历所有候选地址对并执行连通性检测。
  • Controlled 角色(受控方)

    • 定义:被动接收 Answer(如 SRS 服务器响应客户端 Offer)的一方,仅响应控制方的连通性检查请求。
    • 特性:可实现 精简 ICE 流程(Lite ICE Agent),仅接收并回复 STUN Binding Request,不主动发起请求。SDP 中通过 a=ice-lite 字段标识(如 SRS 服务器配置)。
    • 场景:适用于公网部署的服务端(如媒体服务器),利用其公网可达性简化流程,降低资源消耗。

角色协商逻辑
双方通过 SDP 交换候选地址优先级(priority 属性),控制方通过 tie-breaker 机制(随机生成的 32 位无符号整数)解决优先级冲突,确保唯一控制方主导流程。

1.4.2 ICE 模式:Full ICE 与 Lite ICE

Full ICE(完整模式)

  • 流程
    双方均需执行 全量候选地址收集(Host/Srvflx/Relay/Prflx)与 双向连通性检查(发送并接收 Binding Request)。
  • 适用场景
    • 两端均处于复杂 NAT 环境(如对称型 NAT、多层路由)。
    • 需要最大概率实现 P2P 直连(如端到端视频通话)。
  • 技术要点
    • 支持 主动探测(发送 Binding Request 到对端候选地址)与 被动响应(接收对端请求并回复)。
    • 需处理候选地址对的 状态机管理(如 valid/inprogress/failed),确保流程有序推进。

Lite ICE(精简模式)

  • 流程
    仅 Full ICE 一方执行主动探测,Lite ICE 一方仅被动响应 Binding Request,不主动发起探测。
  • 适用场景
    • 一端为公网直连设备(如 SRS 服务器),另一端为内网终端(如浏览器)。
    • 降低服务端资源消耗(如避免公网服务器发起大量探测请求)。
  • 技术要点
    • 通过 a=ice-lite 标识精简模式,对端需兼容该模式(如忽略 Lite 方的主动请求)。
    • 仅支持 单向连通性检查(控制方主动,受控方被动),依赖控制方完成路径验证。

1.4.3 候选地址(Candidate)类型与技术细节

ICE 通过枚举多类型候选地址,构建端到端通信的“地址候选集”,核心类型及其技术特性如下:

** Host Candidate(主机地址)**

  • 定义
    终端本地网络接口的真实地址(IPv4/IPv6),直接对应物理或虚拟网卡(如 eth0/wlo1)。
  • 特征
    • 无需经过 NAT 转换,适用于 同网段直连(如局域网内设备)或 公网直连终端
    • 优先级最高(ICE 优先尝试本地直连以降低延迟)。
  • 应用场景
    浏览器通过 getLocalAddress() 获取的内网地址(如 192.168.1.100:5000)或公网 IP(如 203.0.113.1:5000)。

Server-Reflexive Candidate(服务器反射地址,Srvflx)

  • 定义
    终端通过向 STUN/TURN 服务器 发送 Binding Request,经 NAT 反射后获取的公网地址(如 203.0.113.2:8888)。
  • 特征
    • 依赖 锥形 NAT(Cone NAT) 映射机制,需终端主动向服务器发起请求触发映射。
    • 与服务器 IP 无关,仅反映终端出口 NAT 的公网映射(适用于完全圆锥型/限制型 NAT)。
  • 与 Prflx 的区别
    • 获取途径:Srvflx 通过信令服务器(STUN/TURN)获取,属于 信令流程预收集地址
    • 用途:用于 ICE 初期候选地址池构建,提前知晓 NAT 映射地址。

Relayed Candidate(中继地址)

  • 定义
    终端通过向 TURN 服务器 发送 Allocate 请求获取的中继地址(如 turn-server:443),数据需经 TURN 服务器转发。
  • 特征
    • 强制通过中继传输,适用于 对称型 NAT 或防火墙严格限制 的场景。
    • 优先级最低(因引入中继延迟与服务器资源消耗)。
  • 技术实现
    TURN 服务器分配独立的中继端口,并维护“终端 <-> 服务器 <-> 对端”的转发映射表。

Peer-Reflexive Candidate(对等反射地址,Prflx)

  • 定义
    连通性检查过程中,终端通过接收对端发送的 Binding Request 发现的新地址,反映对端 NAT 为其分配的公网映射(如对端经对称型 NAT 转换后的临时地址)。
  • 特征
    • 动态发现,依赖 双向通信试探(非信令预收集),仅在 ICE 交互中生成。
    • 与 Srvflx 可能相同(如对端为完全圆锥型 NAT,映射地址固定),但获取时机不同(Prflx 属于“探测结果”,Srvflx 属于“信令预配置”)。
  • 应用场景
    当对端为 限制型圆锥 NAT 时,Prflx 地址可验证“是否已通过目标 IP/端口的限制”(如对端需先收到本端请求,才允许反向通信)。

1.5 ice交互

1 收集候选地址:找遍所有“通信门牌号”
就像你要给朋友发快递,得先知道自己所有可能的“地址”——家里地址(Host,直连网卡地址)、小区门卫代收点地址(Srvflx,STUN服务器反射的NAT公网地址)、驿站中转地址(Relay,TURN中继地址)。

  • 怎么做
    • 用STUN服务“问路人”:发个消息给STUN服务器,它会告诉你经过NAT后的公网地址(比如你家小区的门牌号)。
    • 用TURN服务“租驿站”:如果直接送快递不行,就申请一个中继地址(驿站地址),让快递先到驿站再转发。
  • 去重:如果两个地址实际指向同一个地方(比如家里地址和代收点地址重复),就删掉多余的,避免混乱。

2 交换候选地址:互发“通信地址簿”
你和朋友需要交换各自的“地址簿”(候选地址),才能知道对方的所有可能地址。

  • 两种方式
    • SDP交换(快递单附地址):像寄快递时在面单上写明所有地址,通过信令(比如微信消息)发给对方。
    • Trickle方式(分批发地址):先告诉朋友大概要寄东西,边整理地址边分批发送,不用等全部整理完再开始,节省时间。
  • 地址格式示例
    a=candidate:门牌号 房间号 快递类型 优先级 公网地址 端口 typ 地址类型 ...
    (例如:门牌号=240568271,快递类型=UDP,公网地址=174.139.8.82,类型=代收点地址

3 生成候选对:给“地址”配对测试
把你和朋友的地址按“快递类型”(RTP/RTCP、UDP/TCP)配对,就像给钥匙找锁孔,只有类型匹配的地址才能组成“候选对”。

  • 优先级排序
    先试“直连地址”(家里地址),再试“代收点地址”,最后用“驿站地址”(中继),就像寄快递优先选最快的直达路线,不行再中转。
  • 替换规则
    如果是代收点地址(Srvflx),需要用对应的“真实家里地址”(Base地址)替换,确保地址正确。

4 连通性检查:测试“地址”是否能打通
就像打电话测试线路是否通畅,ICE会给每个候选对发“测试包”(STUN Binding Request),看对方是否能收到回复(Response)。

  • 两种测试模式
    • 普通测试:你和朋友各自按优先级顺序尝试拨打对方的地址,就像轮流打电话。
    • 触发测试:收到朋友的测试请求时,立刻回拨,节省时间(比如朋友打给你,你同时回拨过去)。
  • 重传机制
    如果没收到回复,就像电话占线一样,过一会儿再打(RTO重传),每次间隔时间翻倍,直到接通或放弃(最大重传次数)。

5 认证与连接维护:确保“通信安全稳定”

  • 身份验证
    测试包需要“密码”(ice-ufrag和ice-pwd)验证,就像快递需要签名才能签收,防止冒名顶替。
  • 连接保鲜
    接通后,每隔一段时间发个“心跳包”(STUN消息),就像定期和朋友说“你还在吗”,确保连接没断开(初期50ms一次,稳定后2.5秒一次)。

6 提名与选择最终地址:挑出“最佳通信路线”

  • 提名流程
    • 普通提名:先测试所有候选对,选出能用的,再挑出最好的(比如先列出所有能打通的电话,再选信号最稳定的)。
    • 进取提名:每测试一个候选对,就直接标记为“可能最佳”,不用重复测试,适合追求效率的场景。
  • 最终选择
    优先选优先级最高的候选对(比如直连地址),就像选最快的快递路线。确认后,用DTLS建立安全连接(类似给快递上把锁),开始正式传输数据。

通俗总结:ICE就像“跨区快递配送”

  1. 收集地址:整理自己所有可能的发货地址(家里、代收点、驿站)。
  2. 交换地址:和收件人分享地址簿,让对方知道怎么找你。
  3. 配对测试:用不同地址尝试发货,测试哪条路线能通。
  4. 选最佳路线:优先选最快、最稳定的路线(直连→代收点→驿站),确保快递安全送达。

通过这一系列步骤,ICE就能在复杂网络环境中(如多层NAT、防火墙)找到最有效的通信路径,实现端到端的实时连接。

2 SRS中ICE模块

2.1 ICE的三个重要问题

一、ICE交互的目的
ICE(交互式连接建立)的核心目标是解决异构网络环境下的终端连通性问题,具体包括:

  1. NAT穿越:通过收集和验证候选地址(如Host、Srvflx、Relay),突破NAT和防火墙限制,建立端到端通信路径。
  2. 多路径选择:枚举终端所有可能的网络地址(如公网IP、NAT映射地址、中继地址),通过优先级排序和连通性检查,选择最优传输路径。
  3. 角色协商:通过主控方(controlling)和受控方(controlled)角色机制,解决候选地址优先级冲突,确保双方对通信路径达成一致。

二、ICE与SDP的关联
SDP(会话描述协议)是ICE交互的信令载体,二者通过以下方式紧密协同:

  1. 候选地址交换
    • ICE生成的候选地址(如Host、Srvflx、Relay)通过SDP的a=candidate字段传递给对端,例如:
 a=candidate:foundation component protocol priority ip port typ type ...  
  • 其中包含地址类型(type)、优先级(priority)、基础地址(raddr)等关键信息。
  • SDP支持trickle模式(a=ice-options:trickle),允许候选地址分批传输,与媒体协商并行,减少延迟。
  1. 角色与安全参数协商

    • 通过ice-ufragice-pwd字段交换认证信息,用于STUN消息的完整性校验(HMAC-SHA1计算)。
    • 通过a=ice-lite标识Lite ICE模式(如SRS服务器作为受控方),仅被动响应连通性检查。
  2. 流程触发

    • 主动发起方(Offer方)为ICE主控方,被动响应方(Answer方)为受控方,角色通过SDP协商确定。

三、STUN Binding Request/Response的作用
STUN协议是ICE连通性检查的核心工具,通过请求/响应机制验证候选地址对的可达性:

1. Binding Request的作用

  • 地址验证:向对端候选地址发送请求,检测是否可达。例如,主控方按优先级排序候选对,逐个发送请求。
  • 携带关键属性
    • PRIORITY:标识候选对优先级,由公式计算得出,用于排序检查顺序。
    • USE-CANDIDATE:提名该候选对为最终通信路径,加速连接建立(进取型提名模式)。
    • ICE-CONTROLLED/CONTROLLING:声明终端角色,解决角色冲突(如收到487错误时切换角色)。
  • 认证机制:通过USERNAME属性(格式为remote_ufrag:local_ufrag)和MESSAGE-INTEGRITY确保消息合法性。

2. Binding Response的作用

  • 连通性确认:对端收到请求后返回响应,若满足以下条件则视为检查成功:
    • 响应的源地址等于请求的目的地址;
    • 响应的目的地址等于请求的源地址(地址对称性校验)。
  • 反射地址生成:若响应包含映射地址(MAPPED-ADDRESS),则生成对等端反射候选地址(Prflx),补充到候选地址池。
  • 失败处理:若返回错误响应(如487角色冲突),双方切换角色并重新检查候选对。

3. 周期性检查与连接维护

  • 客户端每隔2~3秒发送一次Binding Request,维持连接有效性。
  • 成功响应后,终端以50ms(初期)至2.5秒(稳定后)的间隔发送心跳包,防止连接过期。

总结
ICE通过SDP交换候选地址与角色信息,依托STUN Binding机制验证连通性,最终在复杂网络中建立可靠的媒体传输路径。这一过程中,SDP是信令基础,STUN是技术工具,ICE则是统筹整个流程的核心逻辑。

2.2 ICE的状态

1. Waiting(等待状态)
定义:尚未启动连通性检查,处于候选地址对的“待选”阶段。

  • 核心特征
    • 候选地址对(candidate pair)已生成,但未被选中执行检查。
    • ICE框架从**检查列表(checklist)**中按优先级筛选下一个待检查的地址对。
    • 优先级排序规则:Host(直连地址)> Srvflx(STUN反射地址)> Relay(TURN中继地址)。
  • 触发场景
    • 初始阶段,候选地址收集完成后,尚未开始检查。
    • 检查列表中存在未处理的候选对,等待调度。

2. In-Progress(进行中状态)
定义:连通性检查已启动,但尚未收到响应或完成验证。

  • 核心特征
    • 已向对端候选地址发送 STUN Binding Request,等待 Binding Response
    • 地址对状态标记为“检查中”,期间不允许重复发起检查。
    • 重传机制生效:若未按时收到响应,按 RTO(超时重传时间) 翻倍重传,直至超时或收到响应。
  • 触发场景
    • 主控方(controlling role)按优先级选中候选对,发起首次检查。
    • 因网络延迟等原因,响应尚未返回。

3. Succeeded(成功状态)
定义:连通性检查成功完成,地址对可用于媒体传输。

  • 核心特征
    • 收到对端的 Binding Response,且满足 地址对称性校验(响应源地址=请求目的地址,响应目的地址=请求源地址)。
    • 生成 有效候选对(valid pair),加入 有效列表(validlist),按优先级排序。
    • 主控方根据validlist提名最优路径(如普通提名或进取型提名)。
  • 触发场景
    • 对端正确解析请求并返回合法响应。
    • 验证通过的地址对可直接用于P2P通信或中继传输。

4. Failed(失败状态)
定义:连通性检查失败,地址对不可用。

  • 核心特征
    • 未收到响应(超时)或收到错误响应(如487 Role Conflict、404 Not Found)。
    • 地址对标记为“失败”,从检查列表中移除,不再重试。
    • 若为中继地址(Relay),需释放相关资源(如TURN服务器分配的端口)。
  • 触发场景
    • NAT严格限制(如对称型NAT)导致请求无法穿透。
    • 对端未正确实现STUN协议或地址不可达。

5. Frozen(冻结状态)
定义:连通性检查未启动,且当前不计划执行检查。

  • 核心特征
    • 候选地址对暂不参与检查,可能因优先级过低或资源限制被延迟处理。
    • 与Waiting状态的区别:Frozen状态下地址对未进入检查列表,而Waiting已在列表中等待调度。
  • 触发场景
    • 终端资源不足(如带宽受限),优先处理高优先级地址对。
    • 动态调整检查策略,临时跳过某些地址对。

状态流转逻辑

初始状态 --> Waiting(候选对加入检查列表)  
Waiting --> In-Progress(选中候选对,发起检查)  
In-Progress --> Succeeded(收到有效响应)  
In-Progress --> Failed(超时或错误响应)  
Waiting/Failed --> Frozen(暂不检查或放弃检查)  

2.3 SRS中ICE

更加准确的来说,应该要说SFU中ICE模块的作用,SRS是一个SFU模式的流媒体服务器。

一、SFU 在 ICE 中的角色定位

  • 角色:通常作为 受控方(controlled role),即 Lite ICE Agent
    • 仅被动响应客户端的连通性检查请求,不主动发起 STUN Binding Request。
    • 通过 SDP 中的 a=ice-lite 字段标识,简化资源消耗(如 SRS 服务器)。
  • 职责
    1. 接收客户端发送的 STUN Binding Request,解析并验证请求合法性。
    2. 返回 STUN Binding Response,协助客户端完成连通性检查。
    3. 基于客户端提名的候选对,建立媒体流转发路径。

二、核心工作流程步骤

1. 候选地址收集与交换(客户端主导)
  • 客户端(主控方)
    • 收集本地候选地址(Host/Srvflx/Relay),通过 SDP Offer 发送给 SFU。
    • 示例 SDP 字段:
      a=candidate:240568271 1 udp 1686052607 174.139.8.82 64462 typ srflx ...  
      a=ice-ufrag:K6c3  
      a=ice-pwd:UbMotp8VJxqc37FjOMnQ4Kfa  
      
  • SFU(受控方)
    • 解析客户端的 SDP Offer,提取候选地址,生成本地候选地址(通常为公网 Host 地址)。
    • 通过 SDP Answer 返回本地候选地址(如公网 IP)及 a=ice-lite 标识。

2. 连通性检查(客户端主动发起)

  • 客户端行为
    • 按优先级排序候选对(Host > Srvflx > Relay),生成检查列表(checklist)。
    • 每隔 2~3秒 向 SFU 发送 STUN Binding Request,携带以下关键属性:
      • USERNAME:格式为 SFU_ufrag:客户端_ufrag(通过 SDP 交换的 ice-ufrag)。
      • ICE-CONTROLLING:声明主控方角色。
      • PRIORITY:候选对优先级,用于排序检查顺序。
  • SFU 响应流程
    1. UDP 包处理入口
      • 解析数据包,判断是否为 STUN 请求(data[0] == 00x00 为 Binding Request)。
      • 调用 SrsStunPacket::decode 解析消息头与属性(如 USERNAME、MESSAGE-INTEGRITY)。
    2. 验证与生成响应
      • 校验 MESSAGE-INTEGRITY(使用客户端 ice-pwd 计算 HMAC-SHA1)。
      • 构造 Binding Response,包含:
        • MAPPED-ADDRESS:SFU 的公网 IP 与端口(客户端请求的源地址)。
        • FINGERPRINT:CRC32 校验值,防止协议混淆。
        • ICE-CONTROLLED:标识受控方角色。
    3. 返回响应
      • 通过 SrsStunPacket::encode 生成响应包,发送给客户端。

3. 状态管理与连通性验证

  • 客户端状态更新
    • 若收到 SFU 的 Binding Response 且满足 地址对称性(响应源地址=请求目的地址),则候选对状态设为 Succeeded,加入有效列表(validlist)。
    • 失败时(如超时或错误响应),状态设为 Failed,重试直至最大次数或切换至中继地址。
  • SFU 状态维护
    • 仅被动响应检查,不维护候选对状态机,依赖客户端驱动流程。
    • 若收到无效请求(如角色冲突 487 Error),忽略或返回错误响应,由客户端处理角色切换。

4. 媒体路径建立与转发

  • 提名最优候选对
    • 客户端从 validlist 中选择优先级最高的候选对,通过 USE-CANDIDATE 属性提名(进取型模式)或二次检查(普通模式)。
    • 提名后,客户端与 SFU 基于该地址对建立 DTLS 安全连接,协商媒体传输参数(如 RTP/RTCP 端口)。
  • SFU 转发逻辑
    • 确认连通性后,SFU 作为媒体中继,接收客户端发送的 RTP 流,按订阅关系转发至其他客户端。
    • 定期发送 STUN 心跳包(默认 2.5秒间隔),维持连接有效性,防止 NAT 会话超时。

三、关键技术点

  1. STUN 消息解析
    • SFU 通过 SrsStunPacket::decode 解析请求中的 USERNAMEPRIORITYICE-CONTROLLING 等属性,
  2. 角色与认证
    • 客户端作为主控方,SFU 作为受控方,通过 ICE-CONTROLLING/CONTROLLED 属性区分。
  3. 连通性检查机制
    • 客户端发送请求,SFU 返回响应,验证地址对称性。
  4. Lite ICE 模式
    • SFU 通过 a=ice-lite 标识精简模式,仅响应不主动发起,。

四、SFU 与客户端的交互示意图

客户端(主控方)              SFU(受控方)  
┌───────────────┐            ┌───────────────┐  
│ 生成候选地址   │            │ 解析 SDP      │  
│ 发送 SDP Offer ├───────────▶│ 生成公网候选  │  
└───────────────┘            └───────────────┘  
            ↓ STUN Binding Request (含 USERNAME/PRIORITY)  
┌───────────────┐            ┌───────────────┐  
│ 等待响应      │◀───────────┤ 解析请求      │  
│               │            │ 校验签名      │  
└───────────────┘            └───────────────┘  
            ↑ STUN Binding Response (含 MAPPED-ADDRESS)  
┌───────────────┐            ┌───────────────┐  
│ 更新候选状态   │            │ 无状态维护    │  
│ 选择最优路径   │            │               │  
└───────────────┘            └───────────────┘  
            ↓ 媒体流传输(RTP/RTCP over UDP)  

五、总结
SFU 中的 ICE 流程以 客户端主动、服务器被动响应 为核心,通过 SDP 交换候选地址,利用 STUN 协议完成连通性验证,最终建立可靠的媒体转发路径。该模式充分发挥 SFU 的公网可达性,降低服务器资源消耗,适用于“客户端-服务器”架构的实时通信场景(如视频会议、直播连麦)。

2.4 SRS 实现细节

一、UDP 包处理入口函数
1. SrsRtcServer::on_udp_packet

  • 功能:SRS 处理 UDP 数据包的统一入口**,区分媒体流(RTP/RTCP)与信令包(STUN)。**
  • 调用场景
    • 当客户端或对端设备通过 UDP 发送数据时触发。
    • 优先判断是否为 RTP/RTCP 包(通过协议头特征),若否,则进一步判断是否为 STUN 包。
  • 关键逻辑
    // 判断是否为 RTP/RTCP 包
    if (is_rtp_or_rtcp(data, len)) {
        // 处理媒体流
    } else if (srs_is_stun(data, len)) {
        // 解析 STUN 包
        SrsStunPacket stun_packet;
        stun_packet.decode(data, len);
        session->on_stun(skt, &stun_packet); // 转交连接层处理 STUN 请求
    }
    

二、STUN 消息编解码函数
1. SrsStunPacket::decode

  • 功能:解析 STUN 协议数据包,提取消息类型、属性(如 USERNAME、PRIORITY、ICE-CONTROLLING)及事务 ID。
  • 参数
    • buf:STUN 包字节数组。
    • nb_buf:数据包长度。
  • 解析流程
    1. 读取 20 字节头部:解析消息类型(Message Type)、消息长度(Message Length)、魔术Cookie(Magic Cookie)、事务 ID(Transaction ID)。
    2. 遍历属性字段:处理必选属性(如 USERNAMEMESSAGE-INTEGRITY)和可选属性(如 PRIORITYICE-CONTROLLING)。
  • 关键代码片段
    // 解析消息类型(2 字节)
    uint16_t message_type = stream->read_2bytes();
    // 解析属性部分
    while (stream->remain() >= 4) {
        uint16_t type = stream->read_2bytes();
        uint16_t len = stream->read_2bytes();
        std::string val = stream->read_string(len);
        // 处理不同属性类型(如 USERNAME、PRIORITY 等)
    }
    

2. SrsStunPacket::encode

  • 功能:生成 STUN 响应包(如 Binding Response),填充属性字段并计算校验值。
  • 参数
    • pwd:本地 ice-pwd,用于生成 MESSAGE-INTEGRITY 签名。
    • stream:输出缓冲区,存储编码后的字节流。
  • 关键逻辑
    • 构造头部:设置消息类型(如 Binding Response0x01)、事务 ID(与请求一致)。
    • 添加属性:包括 MAPPED-ADDRESS(SFU 公网地址)、FINGERPRINT(CRC32 校验)、USERNAME(对端 ice-ufrag:本地 ice-ufrag)。
    • 计算 MESSAGE-INTEGRITY:使用 HMAC-SHA1 算法结合 pwd 生成消息摘要。

三、STUN 请求处理函数
1. SrsRtcConnection::on_stun

  • 功能:连接层处理 STUN 请求的核心函数,根据请求类型生成响应并维护连接状态。
  • 调用场景
    • SrsRtcServer::on_udp_packet 解析出 STUN 包后,调用此函数进行业务逻辑处理。
  • 关键流程
    1. 识别请求类型:判断是否为 Binding Request
    2. 验证身份与签名:通过 USERNAME 解析对端 ice-ufrag,使用本地 ice-pwd 校验 MESSAGE-INTEGRITY
    3. 生成响应:调用 SrsStunPacket::encode_binding_response 构造响应包,包含 SFU 的公网地址及端口。
    4. 发送响应:通过 UDP 回传给客户端,完成连通性检查应答。
  • 文档对应:段落、,描述 STUN 请求处理链路及响应生成逻辑。

2. SrsStunPacket::encode_binding_response

  • 功能:专门生成 Binding Response 消息,设置映射地址(MAPPED-ADDRESS)及指纹认证。
  • 关键代码
    // 设置映射地址(客户端请求的源地址,即 SFU 公网可见地址)
    stun_binding_response.set_mapped_address(be32toh(inet_addr(client_ip.c_str())));
    stun_binding_response.set_mapped_port(client_port);
    // 计算指纹(CRC32 校验)
    uint32_t crc32 = srs_crc32_ieee(stream->data(), stream->pos(), 0) ^ 0x5354554E;
    

四、SDP 协商相关函数
1. SDP 解析与 ICE 参数提取

  • 功能:解析 SDP 中的 ICE 相关字段(ice-ufragice-pwdice-lite),存储用于 STUN 认证。
  • 关键逻辑
    • 从 SDP 的 a=ice-ufraga=ice-pwd 字段提取认证信息。
    • 通过 a=ice-lite 标识 Lite ICE 模式,设置 SRS 为受控方(controlled role)。
  • 文档对应:段落、,描述 SDP 中 ICE 参数的作用及协商流程。

五、ICE 状态与连通性管理函数
1. SrsRtcConnection::on_binding_request

  • 功能:处理客户端发起的 Binding Request,更新连接状态并触发响应生成。
  • 调用场景:当解析出 STUN 请求为 Binding Request 时,由 on_stun 调用。
  • 关键操作
    • 记录请求的事务 ID,关联候选地址对。
    • 验证请求中的 PRIORITYICE-CONTROLLING 属性,确认客户端为主控方。
  • 文档对应:段落,描述请求处理与状态关联逻辑。

六、函数调用链路总结

客户端 STUN 请求 → UDP 包到达 SRS → SrsRtcServer::on_udp_packet 解析 → 
判断为 STUN 包 → SrsStunPacket::decode 解析属性 → 
SrsRtcConnection::on_stun 处理业务逻辑 → SrsStunPacket::encode_binding_response 生成响应 → 
SrsRtcServer::on_udp_packet 发送响应 → 客户端接收响应,更新 ICE 状态

七、技术要点与开发建议

  1. Lite ICE 模式适配
    • SRS 作为受控方,仅实现 decodeencode_binding_response 等被动响应函数,无需实现主动发起请求的逻辑。
  2. 性能优化
    • STUN 解析函数需避免阻塞,可通过协程(如 SRS 内置的 SrsFastCoroutine)实现异步处理,参考段落。
  3. 调试关键点
    • 重点监控 on_udp_packet 中的协议分类逻辑,确保 STUN 包不被误判为媒体流。
    • 验证 MESSAGE-INTEGRITY 校验是否正确,避免因签名错误导致连通性检查失败。