eXosip和oSIP 库 (libosip2)的区别
libosip2 (oSIP) 是一个 轻量级、专注于 SIP 协议解析/构建和基础事务管理的库,它不处理网络传输。eXosip 是构建在 libosip2 之上的一个 高层封装库,它实现了完整的 SIP 用户代理 (UA) 核心功能,包括网络传输、完整的事务和对话状态机、事件管理以及应用友好的 API
- oSIP 就像汽车的零部件供应商: 提供高质量的发动机 (SIP 解析/构建)、变速箱 (基础事务管理)、底盘 (对话结构)。
- eXosip 就像整车厂: 它采购 oSIP 的零部件,自己设计制造车身、电气系统、悬挂 (网络传输, 完整状态机),并将所有部件组装成一辆能开上路的完整汽车 (完整的 SIP UA),还提供方向盘、油门踏板 (高层 API) 给司机 (应用开发者) 使用。
协作关系
eXosip 调用 oSIP:
- 解析收到的 SIP 消息: 使用 osip_message_parse() 将收到的字节流转换成结构化的 osip_message_t 对象。
- 构建要发送的 SIP 消息: 使用 oSIP 的各种 osip_xxx_set() 函数和 osip_message_to_str() 构建出符合规范的 SIP 消息字符串。
- 创建和管理事务/对话对象: 使用 osip_transaction_new(), osip_dialog_new() 等 API 创建基础对象,eXosip 在此基础上管理其完整生命周期和状态。
- 处理 URI、头域等: 使用 oSIP 的工具函数解析、操作 SIP URI 和各种头域参数。
eXosip 扩展 oSIP:
- 驱动状态机: oSIP 定义了事务状态 (如 PROCEEDING, COMPLETED, CONFIRMED) 和状态转换 API (如 osip_transaction_set_state()),但 eXosip 是实现状态转换逻辑 (jcall.c, jcallback.c 等) 的引擎。它根据定时器、收到的响应、应用动作等条件,调用 oSIP 的 API 来更新事务/对话状态。
- 提供网络层: extl_udp.c, extl_tcp.c 等模块处理实际的 socket 通信,接收数据后交给 oSIP 解析,并将 oSIP 构建好的消息发送出去。
- 提供高层事件和 API: 将 oSIP 的底层事件 (OSIP_MESSAGE_RECEIVED) 和状态机变化,翻译成 EXOSIP_CALL_INVITE, EXOSIP_CALL_CLOSED 等应用层事件。提供 exosip_call_xxx, exosip_message_xxx 等易用 API 隐藏 oSIP 的复杂性。
- 管理定时器和多线程: 实现定时器调度来驱动事务超时和重传。利用 Pipe 等机制实现线程间通信。
eXosip 框架核心角色
-
ex*.c 文件 (e.g., ex_call.c, ex_message.c): 这些文件实现了 eXosip 面向应用层 的 API (UAC/UAS 功能:呼叫、消息、注册、订阅等)。它们代表了 eXosip 库的“用户接口层”。
-
j*.c 文件 (e.g., jcall.c, jrequest.c, jresponse.c): 这些文件实现了 eXosip 的 核心逻辑层。它们负责:
1 接收来自 ex* API 的调用。 2 与底层 oSIP 库 (libosip2) 进行交互,构建、解析 SIP 消息 (osip_message_t)。 3 处理 SIP 事务、对话状态机。 4 管理定时器。 5调用注册的传输钩子来实际发送/接收消息。 6 处理接收到的 SIP 消息,将其转换为事件 (osip_event_t) 并传递给应用(通常通过 Pipe 或回调)。
-
extl_*.c 文件 (e.g., extl_udp.c, extl_tcp.c): 这些文件实现了 eXosip 的传输适配层。它们包含特定传输协议(UDP, TCP, TLS, DTLS)的发送 (_send_message) 和接收 (_recv_message) 逻辑。它们通过钩子机制向核心逻辑层注册自己。
-
oSIP 库 (libosip2): 这是 eXosip 依赖的基础库,提供:
1 SIP 消息的解析 (osip_message_parse) 和构建 (osip_message_to_str)。 2 SIP URI 解析 (osip_uri_parse)。 3 基本 SIP 头域处理。 4 注意: oSIP 本身不处理网络传输、事务状态机或对话状态机。这些是 eXosip (j*.c) 在其基础上实现的。
eXosip的钩子
在 eXosip 的上下文中,“钩子”指的是一种回调函数注册机制,主要用于解耦核心逻辑 (j*.c) 与具体传输实现 (extl_*.c)。
-
定义钩子接口: 在核心逻辑层 (j*.c 相关的头文件或内部结构) 中,定义一个函数指针类型。这个类型明确规定了传输模块需要实现的函数签名(参数和返回值)。例如,一个用于发送消息的钩子类型可能定义为:typedef int (*transport_send_hook_t)(int socket, const char *dest_host, int dest_port, const char *buf, size_t len);
-
注册表: 核心逻辑层维护一个注册表(通常是一个包含函数指针的结构体或数组),用于存储当前激活的传输钩子。
-
传输模块实现: 每个具体的传输模块 (extl_udp.c, extl_tcp.c 等) 实现符合钩子接口定义的函数。例如,extl_udp.c 实现 udp_send_message 函数,extl_tcp.c 实现 tcp_send_message 函数
-
注册过程: 在 eXosip 初始化阶段 (通常在 exosip_init 或其调用的内部函数中),根据用户的配置或自动探测,核心逻辑层会加载所需的传输模块(编译时链接或动态加载)。然后,核心逻辑层调用传输模块提供的注册函数 (例如 extern int extl_udp_init())。在这个注册函数内部,传输模块将其实现的函数(如 udp_send_message)赋值给核心逻辑层注册表中对应的钩子(如 transport_send_hook)。
-
调用过程: 当核心逻辑层 (jcall.c 等) 需要发送一个 SIP 消息时,它不再关心具体用 UDP 还是 TCP 发送。它只需要查找注册表,找到当前注册的 transport_send_hook 函数指针,然后调用这个函数指针,并传入必要的参数(目标地址、消息内容等)。这个调用最终会路由到实际注册的传输函数(如 udp_send_message)去执行具体的网络发送操作。接收过程通常也类似,传输模块在接收到数据后会调用核心逻辑层提供的回调函数(这本身也是一种钩子)。
-
核心作用与优势:
解耦 (Decoupling): 核心 SIP 状态机和逻辑 (j*.c) 完全独立于底层网络传输细节。添加新的传输协议(如 WebSocket)只需实现新的 extl_ws.c 并注册其钩子,无需修改 j*.c 的核心代码。
灵活性 (Flexibility): 运行时或启动时可以选择和配置使用哪种传输协议。
可维护性 (Maintainability): 传输层的改动不会影响上层 SIP 逻辑,反之亦然。
模块化 (Modularity): 传输协议实现成为可插拔的模块。
eXosip的pipe
在 eXosip 中,Pipe 被用作线程间通信 (Inter-Thread Communication, ITC) 的机制,特别是在多线程模式下。
SIP 协议栈需要同时处理多种任务:
- 网络 I/O (接收 SIP 消息)
- 定时器到期处理
- 执行应用层调用的 API 命令 (如发起呼叫 exosip_call_build_initial_invite)
- 处理内部状态机事件
这些任务如果都在一个线程中顺序处理,可能会阻塞(如等待网络或定时器),导致响应不及时。因此,eXosip 通常设计为多线程模型:
- 主线程 / 应用线程: 调用 eXosip API。
- 工作线程 (eXosip 线程): 执行核心循环,负责网络监听 (select/poll/epoll/kqueue)、定时器检查、事件分发等。
Pipe 在这里主要用于传递“有新任务” 这个信号,而不是传递大量数据。具体任务信息(如要发送的消息内容)通常通过线程安全的队列或共享数据结构(配合互斥锁 mutex 和条件变量 condvar)在应用线程和工作线程之间传递。Pipe 的作用是及时唤醒工作线程去消费这个队列。