架构总览
相关源文件
本页面内容基于以下源文件生成:
SRT(Secure Reliable Transport)是一个开源传输协议,专为超低延迟(亚秒级)音视频流及通用批量数据传输设计。该协议在贡献与分发端点之间提供安全、可靠且自适应的传输能力,能够在抖动和带宽波动的网络环境中维持恒定的端到端延迟。SRT 的三大核心特性——安全加密、丢包恢复和网络自适应——使其成为实时流媒体工作流中的关键基础设施(README.md:24-36)。
SRT 协议实现了 AES 加密以保护媒体流载荷,并提供多种错误恢复机制来最小化典型互联网连接中的丢包。其中,自动重传请求(ARQ)是主要的错误恢复方法:当接收方检测到丢包时,会向发送方发送警报请求重传。此外,协议还支持前向纠错(FEC)和连接绑定等高级特性(README.md:36-38)。
系统架构总览
SRT 的整体架构采用分层设计,从上到下依次为:API 控制层、核心协议层、队列与多路复用层、通道抽象层以及加密子系统。每一层都有明确的职责边界,通过定义良好的接口进行交互。
正在加载图表渲染器...
架构图要点说明:
-
CUDTSocket 与 CUDT 的分层关系:
CUDTSocket是位于CUDT之上的控制层,负责 Socket 生命周期管理,而CUDT是核心协议引擎,处理所有传输逻辑。CUDTSocket拥有CUDT实例(srtcore/api.h:77-81)。 -
CUDT 的组件依赖:
CUDT核心类依赖发送/接收缓冲区(buffer_snd、buffer_rcv)、拥塞窗口(window)、数据包(packet)、队列(queue)、握手模块(handshake)以及拥塞控制(congctl)等关键组件(srtcore/core.h:54-78)。 -
多路复用器的资源整合:
CMultiplexer将发送队列、接收队列、通道和定时器整合在一起,多个 UDT 实例可以共享同一个多路复用器,通过引用计数管理生命周期(srtcore/queue.h:395-618)。 -
通道层的 UDP 封装:
CChannel是对底层 UDP socket 的薄封装,提供open、close、sendto等操作,将 SRT 数据包映射到 UDP 传输(srtcore/channel.h:64-92)。 -
加密子系统的独立性:HaiCrypt 作为独立的加密子系统,通过
HaiCrypt_Handle句柄与核心层交互,支持 AES-128/192/256 以及 GCM 模式(haicrypt/haicrypt.h:30-93)。
核心类层次结构
CUDTSocket 控制层
CUDTSocket 是 SRT API 层的核心类,充当 CUDT 核心功能层的控制包装器。其职责边界明确:负责 Socket 的创建、绑定、连接、监听等生命周期管理操作,但不直接处理数据传输逻辑。
职责边界:
- 管理 Socket 状态机(打开、绑定、连接、监听、关闭)
- 维护 Socket 配置参数
- 协调
CUDT核心实例与底层多路复用器的关联 - 不直接处理数据包的发送与接收
入口与关键 API:
CUDTSocket 通过 CUDTUnited 全局管理器进行实例化和查找。CUDTUnited 维护所有活跃 Socket 的注册表,负责 ID 分配和并发访问控制(srtcore/api.h:71-242)。
关键数据结构:
- Socket ID:唯一标识每个 SRT Socket
- Socket 配置:通过
CUDT实例间接管理 - 状态信息:连接状态、绑定地址等
CUDT 核心协议引擎
CUDT 是 SRT 协议的核心实现类,承载了所有传输逻辑,包括可靠传输、拥塞控制、流量控制、数据包调度等。该类的头文件依赖揭示了其复杂的内部结构(srtcore/core.h:54-78)。
职责边界:
- 实现完整的 SRT/UDT 传输协议
- 管理发送和接收缓冲区
- 执行拥塞控制算法
- 处理握手与连接建立
- 集成数据包过滤与 FEC
- 不直接操作网络 I/O(委托给队列层)
关键依赖组件:
| 组件 | 头文件 | 职责 |
|---|---|---|
| 发送缓冲区 | buffer_snd.h | 管理待发送数据的缓冲与重传 |
| 接收缓冲区 | buffer_rcv.h | 管理接收数据的排序与交付 |
| 拥塞窗口 | window.h | 流量控制与拥塞窗口管理 |
| 数据包 | packet.h | SRT 数据包的封装与解析 |
| 队列 | queue.h | 发送/接收队列的接口 |
| 握手 | handshake.h | 连接握手协议实现 |
| 拥塞控制 | congctl.h | 拥塞控制算法抽象 |
| 包过滤器 | packetfilter.h | FEC 与包过滤功能 |
| 加密 | haicrypt.h | AES 加密/解密集成 |
关键调用链:
数据发送时,CUDT 将应用层数据封装为 CPacket,经过拥塞窗口检查后放入发送缓冲区,再由发送队列调度通过 CChannel 发出。数据接收时,CChannel 从 UDP 接收数据包,经接收队列分发到对应的 CUDT 实例,经过排序、解密后交付应用层。
错误处理与边界条件:
- 拥塞窗口满时,数据发送将被阻塞或缓存
- 接收缓冲区溢出时,根据配置执行丢弃或反压策略
- 握手超时后触发连接失败回调
- 加密失败时丢弃对应数据包并记录错误
传输层与多路复用架构
CChannel UDP 通道封装
CChannel 是对底层 UDP socket 的面向对象封装,为上层提供统一的网络 I/O 抽象。该类隐藏了平台相关的 socket 操作细节(srtcore/channel.h:64-92)。
职责边界:
- 封装 UDP socket 的创建、绑定与关闭
- 提供基于地址的数据包发送接口
- 查询本地与对端 socket 地址
- 管理 UDP 发送/接收缓冲区大小
- 不处理协议逻辑(如重传、排序)
入口与关键 API:
| 方法 | 功能 | 参数 |
|---|---|---|
open(addr) | 绑定到指定本地地址 | sockaddr_any& 本地地址 |
open(family) | 创建指定协议族 socket | int AF_INET/AF_INET6 |
attach(udpsock, addr) | 附加到已有 UDP socket | UDP socket 描述符 + 地址 |
close() | 关闭 UDP 实体 | 无 |
sendto(addr, packet, src) | 发送数据包到指定地址 | 目标地址 + CPacket 引用 + 源地址 |
getSockAddr(addr) | 查询本地 socket 地址 | 输出地址引用 |
getPeerAddr(addr) | 查询对端地址 | 输出地址引用 |
关键数据结构:
sockaddr_any:兼容 IPv4/IPv6 的通用地址结构CPacket:SRT 数据包实体,包含头部与载荷UDPSOCKET:底层 UDP socket 描述符的抽象类型
错误处理:
- socket 创建失败时抛出异常
sendto返回实际发送字节数,失败时返回错误码- 地址查询失败时输出无效地址
发送/接收队列与多路复用器
发送队列 CSndQueue 和接收队列 CRcvQueue 是连接核心协议层与通道层的关键桥梁。多路复用器 CMultiplexer 将队列与通道绑定,允许多个 SRT 连接共享同一个 UDP 端口(srtcore/queue.h:395-618)。
职责边界:
CSndQueue:从CUDT实例收集待发送数据包,调度通过CChannel发出CRcvQueue:从CChannel接收 UDP 数据包,根据 Socket ID 分发到对应CUDTCMultiplexer:管理发送队列、接收队列、通道和定时器的生命周期,维护引用计数
关键数据结构:
CMultiplexer 的核心成员(srtcore/queue.h:586-618):
| 成员 | 类型 | 职责 |
|---|---|---|
m_pSndQueue | CSndQueue* | 发送队列指针 |
m_pRcvQueue | CRcvQueue* | 接收队列指针 |
m_pChannel | CChannel* | UDP 通道指针 |
m_pTimer | CTimer* | 定时器指针 |
m_iPort | int | 绑定的端口号 |
m_iIPversion | int | 地址族(AF_INET/AF_INET6) |
m_iRefCount | int | 关联的 UDT 实例计数 |
m_mcfg | CSrtMuxerConfig | 多路复用器配置 |
m_iID | int | 多路复用器唯一 ID |
关键调用链:
- 发送路径:
CUDT→CSndQueue→CChannel::sendto→ UDP 网络 - 接收路径:UDP 网络 →
CChannel→CRcvQueue→ 根据 Socket ID 查找CUDT→ 数据包入队
生命周期管理:
CMultiplexer 构造函数将所有指针初始化为 NULL,防止内存分配失败时的悬空指针问题。析构函数按序释放接收队列、发送队列、定时器,最后调用 close() 关闭通道。引用计数 m_iRefCount 初始为 1,每个新关联的 UDT 实例递增,断开时递减,归零时销毁多路复用器。
错误处理与边界条件:
- 队列为空时的定时轮询策略
- 多路复用器引用计数归零时的资源清理
resetAtFork()方法处理 fork 后的状态重置
安全加密子系统
HaiCrypt 加密配置与密钥管理
HaiCrypt 是 SRT 的独立加密子系统,提供 AES 加密/解密功能。该模块通过配置结构体和句柄式 API 与核心层交互(haicrypt/haicrypt.h:30-58)。
职责边界:
- 提供 AES-CTR/AES-GCM 模式的加密与解密
- 管理密钥派生(PBKDF2)与密钥包装(RFC 3394)
- 处理密钥材料的周期性刷新与预通告
- 不处理网络传输(由 SRT 核心层负责传输密钥材料消息)
核心加密常量:
| 常量 | 值 | 含义 |
|---|---|---|
HAICRYPT_CIPHER_BLK_SZ | 16 | AES 块大小(字节) |
HAICRYPT_PWD_MAX_SZ | 80 | 密码最大长度 |
HAICRYPT_KEY_MAX_SZ | 32 | 密钥最大长度(AES-256) |
HAICRYPT_SALT_SZ | 16 | 盐值大小 |
HAICRYPT_AUTHTAG_MAX | 16 | 认证标签最大长度(GCM) |
HAICRYPT_AAD_MAX | 16 | 附加认证数据最大长度 |
HAICRYPT_PBKDF2_SALT_LEN | 8 | PBKDF2 盐值长度 |
HAICRYPT_PBKDF2_ITER_CNT | 2048 | PBKDF2 迭代次数 |
关键数据结构:
HaiCrypt_Secret 定义了安全关联的密钥材料(haicrypt/haicrypt.h:51-58):
| 字段 | 类型 | 职责 |
|---|---|---|
typ | unsigned | 密钥类型:0=未定义,1=预共享 KEK,2=密码 |
len | size_t | 密钥材料长度 |
str | unsigned char[] | 密钥材料内容(最大 HAICRYPT_SECRET_MAX_SZ) |
HaiCrypt_Cfg 定义了加密会话的完整配置(haicrypt/haicrypt.h:60-85):
| 字段 | 类型 | 职责 |
|---|---|---|
flags | unsigned | 配置标志位(TX/RX/加密/FEC/GCM) |
secret | HaiCrypt_Secret | 安全关联密钥材料 |
cryspr | HaiCrypt_Cryspr | 加密服务提供者实例 |
key_len | size_t | SEK 长度(默认 16 字节 = AES-128) |
data_max_len | size_t | 最大数据包长度(默认 1500 字节) |
xport | int | 传输模式:0=独立,1=SRT |
km_tx_period_ms | unsigned int | 密钥材料发送周期(默认 1000ms) |
km_refresh_rate_pkt | unsigned int | 密钥刷新率(默认 0x1000000 包) |
km_pre_announce_pkt | unsigned int | 密钥预通告包数(默认 0x1000 包) |
配置标志位:
| 标志 | 值 | 含义 |
|---|---|---|
HAICRYPT_CFG_F_TX | 0x01 | 发送方向(否则为接收) |
HAICRYPT_CFG_F_CRYPTO | 0x02 | 执行加密操作 |
HAICRYPT_CFG_F_FEC | 0x04 | 启用前向纠错 |
HAICRYPT_CFG_F_GCM | 0x08 | 使用 AES-GCM 模式 |
加密句柄与方向:
HaiCrypt_Handle 是不透明句柄类型,内部指向 hcrypt_Session_str 结构。加密方向通过 HaiCrypt_CryptoDir 枚举区分:HAICRYPT_CRYPTO_DIR_RX(接收解密)和 HAICRYPT_CRYPTO_DIR_TX(发送加密)(haicrypt/haicrypt.h:87-93)。
错误处理与边界条件:
- 密钥材料长度超过
HAICRYPT_SECRET_MAX_SZ时截断 - 数据包长度超过
data_max_len时拒绝加密 - 密钥刷新周期到达时自动生成新密钥并预通告
- GCM 模式下认证失败时丢弃数据包
数据流与调用链
以下时序图展示了 SRT 发送端从应用层调用 send 到数据包通过 UDP 传出的完整数据流,以及接收端从 UDP 收包到应用层调用 recv 获取数据的反向流程。
正在加载图表渲染器...
数据流要点说明:
-
发送路径的分层委托:应用层调用
send后,CUDTSocket将请求委托给CUDT核心。CUDT负责数据包封装、拥塞窗口检查,然后调用 HaiCrypt 加密载荷,最后通过发送队列调度发送(srtcore/api.h:77-81)。 -
加密在发送队列之前执行:加密操作在
CUDT核心层完成,确保进入发送队列的数据包已经是密文。这样发送队列无需感知加密状态(haicrypt/haicrypt.h:60-65)。 -
接收路径的 Socket ID 路由:接收队列从
CChannel获取原始 UDP 数据包后,根据 SRT 头部中的 Socket ID 查找目标CUDT实例,实现多路复用(srtcore/queue.h:395-618)。 -
通道层的无状态转发:
CChannel仅执行sendto操作,不维护连接状态或重传逻辑。所有可靠性保证由上层CUDT实现(srtcore/channel.h:114-120)。 -
解密在排序之前执行:接收端先解密再排序,确保排序逻辑处理的是明文序列号,避免加密后序列号不可用的问题。
-
ARQ 重传的闭环:当接收端检测到丢包时,通过 SRT 控制包(NAK/ACK)通知发送端,发送端从发送缓冲区中取出对应数据包重传,整个重传路径不经过应用层。
核心设计决策与取舍
1. 基于 UDP 的用户态协议栈
SRT 选择在 UDP 之上构建可靠传输协议,而非修改内核协议栈。这一决策使得 SRT 可以在用户态快速迭代,无需操作系统内核支持,同时保持对 UDP 生态(NAT 穿透、防火墙兼容)的兼容性。代价是额外的用户态/内核态数据拷贝开销。
2. 多路复用器的共享与引用计数
CMultiplexer 设计允许多个 SRT Socket 共享同一个 UDP 端口和发送/接收队列。通过引用计数 m_iRefCount 管理生命周期,最后一个 Socket 关闭时才销毁多路复用器。这减少了端口消耗和线程开销,但增加了多路复用器的并发复杂度(srtcore/queue.h:586-604)。
3. 加密子系统的独立抽象
HaiCrypt 作为独立模块,通过 HaiCrypt_Cryspr(加密服务提供者)接口支持不同的加密后端实现。这种抽象允许在不同平台上使用硬件加速(如 AES-NI)或软件回退,但增加了间接调用开销(haicrypt/haicrypt.h:30-32)。
4. 密钥材料的带内传输
SRT 选择通过 SRT 协议本身传输密钥材料,而非依赖外部密钥分发机制。HaiCrypt_Cfg 中的 km_tx_period_ms(默认 1000ms)和 km_refresh_rate_pkt(默认 0x1000000 包)控制密钥材料的周期性发送和刷新。这简化了部署但要求协议本身具备足够的安全性(haicrypt/haicrypt.h:79-84)。
5. CUDTSocket 与 CUDT 的双层架构
将 Socket 管理(CUDTSocket)与协议逻辑(CUDT)分离,使得同一个协议引擎可以被不同的 API 层使用。CUDTSocket 处理 Socket 生命周期和配置,CUDT 专注传输逻辑。这种分离提高了代码可维护性,但也增加了间接层(srtcore/api.h:79-81)。
6. 已知限制
- SRT 当前不支持多路径传输(Multipath),所有数据流通过单一 UDP 连接传输
- FEC 作为包过滤器的扩展实现,与核心传输逻辑的耦合度较高
CChannel的CONID()方法当前返回空字符串,表明通道层与 Socket ID 的关联尚未完全实现(srtcore/channel.h:69-73)
技术选型表格
| 技术 | 用途 | 选型理由 | 替代方案 |
|---|---|---|---|
| UDP | 底层传输协议 | 低延迟、无连接开销、NAT 友好 | TCP(延迟高)、SCTP(部署少)、QUIC(生态不成熟) |
| AES-CTR | 默认加密模式 | 硬件加速支持好、流式加密适合媒体传输 | AES-CBC(需要填充)、ChaCha20(硬件支持弱) |
| AES-GCM | 认证加密模式 | 同时提供加密与完整性验证 | AES-CTR + HMAC(两遍运算开销大) |
| PBKDF2 | 密码派生密钥 | 标准成熟、迭代次数可配置 | Argon2(更新但库依赖重)、bcrypt(不适合密钥派生) |
| RFC 3394 Key Wrap | 密钥包装 | NIST 标准的密钥加密密钥方案 | 自定义密钥包装(安全性难验证) |
| 用户态协议栈 | 协议实现位置 | 跨平台、快速迭代、无需内核支持 | 内核模块(部署复杂)、DPDK(硬件依赖重) |
| 多路复用器模式 | 连接管理 | 多连接共享端口、减少资源消耗 | 每连接独立端口(端口耗尽风险) |
| 引用计数 | 生命周期管理 | 自动化共享资源管理、零开销 | 垃圾回收(延迟不可控)、手动管理(易出错) |
模块依赖关系图
以下图表展示了 SRT 核心模块之间的依赖方向,从上层 API 到底层网络 I/O 的完整依赖链。
正在加载图表渲染器...
依赖关系要点说明:
-
CUDT 是依赖中心:几乎所有核心组件都直接依赖
CUDT,而CUDT反向依赖缓冲区、窗口、数据包、拥塞控制、握手、过滤器和加密等模块(srtcore/core.h:54-78)。 -
队列层的双向依赖:
CSndQueue和CRcvQueue既被CUDT依赖(作为数据传输通道),也被CMultiplexer依赖(作为生命周期管理对象)(srtcore/queue.h:395-618)。 -
通道层的单一依赖者:
CChannel仅被发送队列和接收队列直接使用,CUDT不直接操作通道,保持了 I/O 层的隔离(srtcore/channel.h:64-92)。 -
加密子系统的叶子节点特征:HaiCrypt 不依赖任何 SRT 核心模块,是完全独立的加密库,仅通过
CUDT的调用向上提供服务(haicrypt/haicrypt.h:30-93)。
关键配置与启动流程
加密配置
SRT 的加密行为通过 HaiCrypt_Cfg 结构体进行配置。以下是一个典型的加密配置流程:
c1// 1. 获取加密服务提供者实例 2HaiCrypt_Cryspr cryspr = HaiCryptCryspr_Get_Instance(); 3 4// 2. 配置加密参数 5HaiCrypt_Cfg cfg; 6cfg.flags = HAICRYPT_CFG_F_TX | HAICRYPT_CFG_F_CRYPTO; // 发送方向 + 启用加密 7cfg.cryspr = cryspr; 8cfg.key_len = HAICRYPT_DEF_KEY_LENGTH; // 16 字节 = AES-128 9cfg.data_max_len = HAICRYPT_DEF_DATA_MAX_LENGTH; // 1500 字节 10cfg.xport = HAICRYPT_XPT_SRT; // SRT 传输模式 11 12// 3. 设置密钥材料 13cfg.secret.typ = HAICRYPT_SECTYP_PASSPHRASE; // 使用密码模式 14cfg.secret.len = password_length; 15memcpy(cfg.secret.str, password, cfg.secret.len); 16 17// 4. 配置密钥刷新策略 18cfg.km_tx_period_ms = HAICRYPT_DEF_KM_TX_PERIOD; // 1000ms 19cfg.km_refresh_rate_pkt = HAICRYPT_DEF_KM_REFRESH_RATE; // 0x1000000 包 20cfg.km_pre_announce_pkt = HAICRYPT_DEF_KM_PRE_ANNOUNCE; // 0x1000 包
多路复用器初始化
CMultiplexer 的初始化流程遵循严格的顺序:构造时所有指针置 NULL,随后依次创建通道、定时器、发送队列和接收队列,最后绑定到指定端口。引用计数从 1 开始,每个新关联的 SRT Socket 递增计数(srtcore/queue.h:595-616)。
连接建立流程
SRT 连接建立涉及以下关键步骤:
- 创建 Socket:通过
CUDTUnited创建CUDTSocket,内部实例化CUDT - 配置参数:设置加密、传输、缓冲区等参数
- 绑定多路复用器:将 Socket 关联到现有或新建的
CMultiplexer - 执行握手:通过
handshake模块交换连接参数和密钥材料 - 启动数据传输:握手完成后,发送/接收队列开始工作
密钥生命周期管理
SRT 的密钥管理遵循"预通告-激活-过期"三阶段模型:
- 预通告阶段:新密钥在
km_pre_announce_pkt(默认 4096)个包之前开始发送 - 激活阶段:新密钥在
km_refresh_rate_pkt(默认 16777216)个包后激活 - 过期阶段:旧密钥在预通告期结束后失效
这种设计确保了密钥切换过程中的零中断传输,接收端在过渡期间可以同时使用新旧密钥解密。
