文章目录
eXosip时序图
eXosip 是 OSIP 的 “上层封装”,eXosip 管 “业务交互”:你直接调用 eXosip_call_invite()、eXosip_register() 等 API,不用关心 OSIP 底层事务。
OSIP 管 “协议细节”:定时器、事务(Transaction)、消息解析等逻辑,eXosip 内部通过 OSIP 库实现。比如 eXosip_execute 里的 osip_timers_*_execute,就是 OSIP 的核心逻辑。
eXosip主要流程
1. 配置基础参数
操作:设置 exosip 包容量、网关 IP(v4/v6)、UA 标识等(对应 设置 exosip 中的几个常量…)。
作用:
给 SIP 协议栈 “定规矩”:比如包容量决定能处理多大的 SIP 消息(避免溢出)。
user_agent 是客户端标识(服务端靠它识别你的设备 / 程序,类似 HTTP 的 User-Agent)
2. 激活 eXosip 状态
操作:eXosip_stop = 0(标记为 “允许运行” 状态)。
作用:
- 相当于给 eXosip “开机”,让后续流程(定时器、消息处理)能正常执行。
- 如果设为 1,eXosip 会进入 “停止待销毁” 状态,拒绝处理新逻辑。
3. 初始化进程间通信
操作:用 pipe 初始化多线程通信的信号 / 信号线程(对应 初始化多线程使用的信号和信号线程通信方式 pipe)
作用:定时器线程触发事件后,通过 pipe 通知业务线程处理。
4. 绑定 OSIP 核心库
操作:初始化 exosip 中的 osip(对应 初始化 exosip 中的 osip,用于管理 osip lib 库)
作用:
eXosip 是 OSIP 的 “上层封装”,这一步把两者绑定,让 eXosip 能调用 OSIP 的核心功能(事务管理、协议解析等)。
简单说:OSIP 是 “发动机”,eXosip 是 “方向盘 + 仪表盘”,这步就是把发动机装到车上。
5.注册回调函数
操作:初始化 osip 的 callback 函数(对应 初始化 osip 的 callback 回调函数)。
作用:让 OSIP 事件能 “通知” 到你的业务代码:比如收到 INVITE 时,触发你写的 on_invite_received 函数。
常见回调:呼叫事件、注册事件、消息收发事件(业务逻辑和协议栈解耦的关键)
6.初始化传输层
操作:配置 4 种传输方式(UDP/TCP/TLS/WS 等,对应 初始化传输层的 4 种不同的传输方式)。
作用:决定 SIP 消息怎么发:比如 UDP 适合低延迟(但不可靠),TCP 适合可靠传输(但稍慢)。
eXosip_execute 流程拆解
把大流程拆成 「消息监听 → 定时器 / 事务处理 → 资源清理 → 保活」 四个核心阶段,梳理每个环节的作用:
- 消息监听阶段:eXosip_read_message
- 定时器与事务处理阶段(OSIP 核心逻辑)
- 资源释放清理阶段
- UDP 保活(可选):_eXosip_keep_alive
1. 消息监听阶段:eXosip_read_message
作用:线程持续监听 “是否有新消息到达客户端”,是整个流程的 “入口触发器”。
特殊点:
接收消息是 “被动监听”,但发送消息由管理程序主动调用接口触发(比如业务层调用 eXosip_message_send 这类接口)。
收到消息后,可能触发:
新建 call、transaction(事务)
生成 transaction 事件、exosip 事件(最终上报给管理程序处理)。
事件定义:所有事件类型在 exosip.h 的 eXosip_event_type 枚举里,业务层根据枚举做不同逻辑(比如 EXOSIP_CALL_INVITE 处理呼叫邀请)。
2.定时器与事务处理阶段(OSIP 核心逻辑)
消息预处理后(或无新消息时),进入 OSIP 事务定时器 + 事件执行 环节,分两步:
- osip_timers_*_execute:遍历 4 类事务链表(_ict/_nict/_ist/_nist)的定时器,超时则生成新定时器事件,丢进事务的事件队列。
- osip_*_execute:遍历事务链表,执行队列里的事件(比如消息后半段处理、状态变更)。
- 资源释放清理阶段
处理完事务后,开始 “收尾工作”,释放已结束的 call、registration、publication:
registration/publication 释放时,不会直接销毁事务,而是把事务从自身结构中移除,丢到 exosip 全局的 j_transaction 链表。
真正的事务销毁在 eXosip_release_terminated_calls 里统一处理(延迟释放,保证流程完整性)
UDP 保活(可选)
触发条件:如果传输层用 UDP,必须调用它!
作用:给 registration 发心跳报文,维持 UDP 端口活性(防止系统因超时空闲关闭端口,导致后续消息收不到)
eXosip_read_message 处理流程
流程总览:
传层读消息 → 解析 + 回调 → 事务 / 请求判断 → 分类型处理(INVITE/ACK 等)
1. 传输层读消息
目标:
根据传输层协议(UDP/TCP 等),从协议栈底层读取 SIP 消息。
核心逻辑:
eXosip_read_message 会调用传输层的 tl_read_message(如 exTL_udp_tl_read_message、exTL_tcp_tl_read_message)。
不同传输层走不同分支(UDP/TCP 逻辑分离)。
2.消息预处理
目标:解析消息、回调业务函数、做基础校验。
3. 事务匹配与合法性检查
目标:判断消息属于已有事务 / 对话,还是新请求 / 非法响应。
事务匹配:用 Call-ID、CSeq、Via branch 等字段,找已有事务(Transaction)。
请求 / 响应判断:SIP 请求的 Status 为 0,响应则带具体状态码(200、404 等)
4. 业务逻辑分发
目标:根据消息类型(INVITE/ACK/BYE 等),走不同业务流程。
核心分支(以 eXosip_process_newrequest 为例):
INVITE 细分逻辑(最复杂,需处理对话 / 事务创建):
5.关键点
- 事务 vs 对话的关联
事务:单次请求 - 响应(如 INVITE-200-ACK 是两个事务:INVITE 事务 + ACK 事务)。
对话:多个事务的持续会话(Call-ID 关联,维护媒体协商、CSeq 等状态)。
核心判断:先匹配对话(条件更松,Call-ID 即可),再匹配事务(需 CSeq、Via branch 精确匹配)。 - 非法消息处理
ACK 无对话:直接释放(ACK 必须关联已有对话,否则非法)。
响应无事务:走 eXosip_process_response_out_of_transaction(协议错误,可能是旧响应重传)。
请求类型非法:如 INFO 无对话时回复 481,直接拒绝。 - 协议合规性(SIP RFC 3261 映射)
100 Trying 触发:INVITE 处理时,默认先发 100(除非显式关闭)。
CSeq 校验:新请求的 CSeq 必须大于对话中保存的,否则视为重复请求。
对话状态校验:已结束的对话(收到 BYE 后)再收请求,回复 481。
eXosip_process_response_out_of_transaction 处理流程
把处理流程拆成 「合法性校验 → 对话 / 呼叫匹配 → 分场景处理 → 资源清理」,每个阶段聚焦核心动作
1 应答合法性校验(阶段 A)
- 目标:过滤非法应答(格式错误、关键头域缺失等)。
- 核心逻辑:
检查 SIP 应答的基础格式:Status-Line 是否合法(如 SIP/2.0 200 OK 格式)。 - 校验关键头域:Call-ID、CSeq、Via 是否存在且格式正确。
- 协议依据:SIP RFC 3261 第 7 节(消息格式)、第 17 节(事务处理)。
2 查询关联 Call/Dialog(阶段 B)
- 目标:找到应答所属的呼叫(Call)或对话(Dialog)。
核心动作: - 遍历所有 Call,检查应答的 Call-ID 是否匹配。
对匹配的 Call,进一步检查 Dialog(已建立 Dialog 则校验 To/From Tag、Route 等)。 - 协议逻辑:Call 是对话的容器,Dialog 是 Call 内的会话上下文(关联多次事务)。
3 无匹配处理(阶段 C→D)
- 场景:应答的 Call-ID 与任何 Call 都不匹配。
- 处理:直接释放 SIP 消息(非法应答,与当前系统无关)。
4 分场景处理(阶段 C→E)
根据匹配结果(匹配到 Dialog 或仅匹配到 Call),分两种核心场景:
- 场景 1:匹配到 Dialog(重复 200 应答处触发条件:
应答匹配到已存在的 Dialog,但未匹配到事务(事务已销毁,但 Dialog 仍存在)。
常见原因:对端未收到 ACK,重发 200 OK,导致本端事务已释放但 Dialog 还在。
- 场景 2:仅匹配到 Call(Dialog 未建立时的错误应答)
触发条件:
应答匹配到 Call,但 Dialog 未建立(如 INVITE 事务还未完成 Dialog 初始化)
5 小结
「校验应答合法性 → 找关联 Call/Dialog → 无匹配则释放 → 匹配 Dialog 则补发 ACK → 仅匹配 Call 则临时 Dialog + BYE 清理」
每个步骤都围绕 “修复协议异常” 和 “保证会话一致性” 设计,理解后可快速定位两类问题:
- 重复 200 OK 导致的网络风暴(需检查 ACK 补发逻辑)。
- 异常 200 OK 导致的会话残留(需检查临时 Dialog + BYE 清理逻辑)。
eXosip_automatic_action 处理流程
eXosip_automatic_action 函数的核心是处理认证失败和需要重定向的场景,通过自动重试机制提高通信成功率。其设计遵循 “错误恢复 + 预防性重注册” 双策略,覆盖 SIP 中的核心事务类型(Call、Register、Notify 等)
Call 处理逻辑详解
关键步骤:
-
过滤无效 Call:跳过 c_id < 1 的已删除 Call
-
处理未建立 Dialog 的 Call:检查初始 INVITE 事务状态(是否终结)
-
检查响应状态码:
401/407:认证失败,提取认证信息重试(最多 3 次)
300-399:重定向,更新目标地址重试(最多 3 次) -
处理已建立 Dialog 的 Call:遍历所有 Dialog,重复上述状态码检查逻辑
for (each call in j_calls) {
if (call->c_id < 1) continue; // 跳过已删除Call
if (dialog_not_created(call)) {
// 处理未建立Dialog的初始INVITE
if (transaction_terminated(call) &&
!call_timeout(call, 120) &&
(response_code == 401 || response_code == 407)) {
retry_with_auth(call); // 带认证重试
} else if (response_code >= 300 && response_code <= 399) {
retry_with_redirect(call); // 带重定向重试
}
}
// 遍历并处理Call中的所有Dialog
for (each dialog in call) {
check_and_retry_dialog(dialog); // 同上逻辑
}
}
code
初始化上下文
// 1. 初始化 eXosip 上下文
eXosip_t *exosip = eXosip_malloc();
eXosip_init(exosip);
// 2. 配置基础参数(包容量、UA、网关等)
eXosip_set_user_agent(exosip, "MySIPClient/1.0");
eXosip_set_gateway(exosip, "udp://192.168.1.100:5060");
// 3. 初始化传输层(UDP/TCP 等)
eXosip_listen_addr(exosip, IPPROTO_UDP, "0.0.0.0", 5060, 0);
eXosip_execute 中事件循环主逻辑
while (is_running) { // is_running 由业务控制(比如用户没挂断)
eXosip_event_t *event = eXosip_execute(exosip); // 执行一次事件循环
if (event == NULL) {
usleep(100000); // 无事件时休眠,避免 CPU 空转
continue;
}
// 根据事件类型处理业务逻辑
switch (event->type) {
case EXOSIP_CALL_INVITE:
// 收到新呼叫邀请,处理振铃、媒体协商
handle_invite(event);
break;
case EXOSIP_CALL_ANSWER:
// 被叫应答,开始媒体流传输
handle_answer(event);
break;
// 其他事件类型(注册成功、消息接收等)...
}
eXosip_event_free(event); // 释放事件内存
}
eXosip_execute 是 “心脏”:每次执行会处理消息、定时器、资源释放,驱动 SIP 状态机前进。
回调 + 事件循环结合:事件循环拿到事件后,通过回调函数(初始化阶段注册的)通知业务层