news 2026/10/18 22:05:52

Osip SIP 协议栈实战:sipRecvDemo 与 sipSendDemo 编译运行及避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Osip SIP 协议栈实战:sipRecvDemo 与 sipSendDemo 编译运行及避坑指南

简介:本资源为基于Osip开源SIP库的通信示例工程,面向具备一定C/C++基础、希望理解SIP信令交互与Osip封装用法的开发者。包内围绕sipSendDemo与sipRecvDemo两个示例,展示SIP消息的构建、发送、接收与解析流程,并给出Osip封装类的初始化、消息组装、网络收发及解码等核心方法,便于对照理解INVITE、REGISTER等请求与200 OK、404等响应的处理逻辑。资源共166个文件,以112个h头文件、18个lib库文件、12个in配置、10个am构建脚本及6个cpp源文件为主,另含vcproj、sln工程文件,压缩包约1.64MB,目录结构清晰,可直接用于工程编译与调试参考。目前已有295人学习。通过该示例可快速掌握Osip库的调用方式与SIP通信基本模型,为自建SIP应用打下基础。

1. 从两个可执行文件说起:Osip 的 demo 到底能跑出什么

很多人第一次接触 Osip,都是被sipRecvDemo和sipSendDemo这两个名字带进来的。它们不是库,不是框架,而是 Osip 源码树里两个最朴素的示例程序:一个负责收 SIP 消息,一个负责发 SIP 消息。你把它编译出来跑起来,屏幕上会打印出对端发来的请求行、头域、SDP 体,或者反过来,把一条构造好的 INVITE、REGISTER 发出去。这就是 Osip 的 demo 最直接的价值——它把 SIP 协议栈从“协议文档里的抽象层”拉回到“你能看见字节流”的层面。

如果你正在做 SIP 信令相关的开发,比如软电话、网关、SBC 的呼叫控制模块,或者单纯想搞清楚 SIP 事务层到底怎么处理重传和超时,这套 demo 就是最省事的起点。它不依赖复杂的媒体框架,不牵扯 RTP,只聚焦信令。适合谁?适合已经能看懂 SIP 基本头域、但还没亲手用代码收发过消息的工程师,也适合想快速验证 Osip 编译环境是否正常的熟手。下面我从编译、运行、改参数到排错,把这两个 demo 拆开讲一遍。

2. 编译与运行:把 sipRecvDemo 和 sipSendDemo 跑起来

2.1 先确认 Osip 的依赖和目录结构

Osip 本身是纯 C 写的,依赖很少,常见做法是只依赖 libc 和 pthread。你拿到的源码包解压后,通常会有src/、include/、example/这几个目录。sipRecvDemo和sipSendDemo一般放在example/或者src/test/下。先别急着 make,看一眼configure.ac或Makefile.am里有没有--enable-demo之类的开关。有些版本默认不编译 demo,需要显式打开。

我一般会先跑一遍autoreconf -i,再./configure,最后make。如果源码包里已经带了configure脚本,那就跳过 autoreconf。编译完成后,在example/或src/test/下找sipRecvDemo和sipSendDemo这两个可执行文件。找不到就find . -name "sip*Demo"搜一下。

# 进入源码根目录后,常见编译流程 autoreconf -i # 如果已有 configure 可跳过 ./configure --disable-tls # demo 阶段先关掉 TLS,减少依赖 make -j4 # 编译完成后查找可执行文件 find . -name "sipRecvDemo" -o -name "sipSendDemo"

这里--disable-tls不是必须的,但如果你本机没有装 OpenSSL 开发包,关掉能省去一堆头文件报错。-j4是并行编译,机器核少就改成-j2。编译过程中如果报undefined reference to pthread_create,说明链接时没带-lpthread,去Makefile里找LDFLAGS加上就行。

2.2 sipRecvDemo 的启动参数与收包逻辑

sipRecvDemo的典型用法是绑定一个本地 UDP 端口,然后死循环收 SIP 消息。不同版本的参数名可能略有差异,常见的是-p指定端口,-i指定监听地址。跑起来之后,它会打印收到的原始报文,包括起始行、Via、From、To、Call-ID、CSeq 这些头域。

# 常见启动方式:监听 5060 端口 ./sipRecvDemo -i 0.0.0.0 -p 5060 # 如果参数不对,直接运行看 usage ./sipRecvDemo -h

启动后你会看到类似received message from 192.168.1.100:5060的输出,紧接着是完整的 SIP 文本。这里有个细节:Osip 的 demo 通常只做消息解析和打印,不会自动回 100 Trying 或 200 OK。也就是说,如果你用别的 SIP 客户端向它发 INVITE,对方会一直重传,直到超时。这不是 bug,是 demo 的定位——只演示收,不演示事务状态机。

参数说明:-i 0.0.0.0表示监听所有网卡,生产环境别这么干,demo 阶段无所谓。-p 5060是 SIP 默认端口,如果本机已经有服务占了,换成 5061 或 15060。收包缓冲区大小一般在代码里写死,常见是 4096 或 8192 字节,如果你要收带大 SDP 的 INVITE,去源码里把char buf[4096]改大一点。

2.3 sipSendDemo 的目标地址与消息构造

sipSendDemo反过来,它需要你指定目标 IP 和端口,然后构造一条 SIP 消息发出去。常见参数是-i目标地址、-p目标端口,有的版本还支持-m指定方法(INVITE、REGISTER、OPTIONS)。默认情况下它可能发一条最简单的 OPTIONS 或 REGISTER。

# 向 192.168.1.100 的 5060 端口发一条 SIP 消息 ./sipSendDemo -i 192.168.1.100 -p 5060 # 指定方法为 OPTIONS(如果支持) ./sipSendDemo -i 192.168.1.100 -p 5060 -m OPTIONS

发出去之后,如果你在目标机器上用 tcpdump 或 Wireshark 抓包,能看到完整的 SIP 请求。这里的关键是:demo 里的消息是硬编码的,From、To、Call-ID 都是写死的字符串。你要改内容,得去源码里找osip_message_t的构造部分。常见做法是先用osip_message_init初始化,再逐个osip_message_set_*填头域,最后osip_message_to_str转成字符串发出去。

参数说明:-i后面跟的是点分十进制 IP,别填域名,demo 不做 DNS 解析。-p是目标端口,如果目标没监听,sendto 不会报错,但你会看到 ICMP port unreachable。-m如果版本不支持,就忽略,直接改源码里的方法字符串。

3. 改代码:从 demo 到能用的最小 SIP 端点

3.1 理解 osip_message_t 的构造与解析流程

Osip 的核心数据结构是osip_message_t,它把 SIP 消息的每个部分都映射成结构体字段。收消息时,sipRecvDemo调用osip_message_init初始化一个空消息,然后用osip_message_parse把原始缓冲区解析进去。解析成功后,你可以用osip_message_get_from、osip_message_get_to、osip_message_get_call_id这些函数取头域。发消息时反过来,先osip_message_init,再osip_message_set_from、osip_message_set_to,最后osip_message_to_str序列化成文本。

// 收消息侧的核心调用链(简化) osip_message_t *msg; osip_message_init(&msg); int ret = osip_message_parse(msg, buffer, strlen(buffer)); if (ret == 0) { osip_from_t *from = osip_message_get_from(msg); // 打印 from->url->username 等字段 } osip_message_free(msg);

这段代码里,osip_message_parse的返回值很关键:0 表示成功,非 0 表示解析失败。常见失败原因是缓冲区里有非 SIP 文本,或者头域格式不合法。osip_message_free必须调用,否则每收一条消息泄漏一次内存,跑久了必崩。osip_from_t里的url字段是osip_uri_t类型,取用户名用osip_uri_get_username,取主机用osip_uri_get_host。

3.2 用 osip_message_set_* 构造一条 INVITE

发 INVITE 比发 OPTIONS 复杂一点,因为要带 SDP。但 demo 阶段可以先不带 SDP,只发一个空的 INVITE,看对端怎么回。构造顺序一般是:先设 From、To、Call-ID、CSeq,再设 Via,最后设 Contact 和 Content-Type。

// 构造一条最小 INVITE 的片段 osip_message_t *invite; osip_message_init(&invite); osip_message_set_method(invite, "INVITE"); osip_message_set_from(invite, "sip:alice@example.com"); osip_message_set_to(invite, "sip:bob@example.com"); osip_message_set_call_id(invite, "1234567890@1.2.3.4"); osip_message_set_cseq(invite, "1 INVITE"); // Via 需要填本地地址和端口 osip_message_set_via(invite, "SIP/2.0/UDP 192.168.1.50:5060;branch=z9hG4bK123"); // 序列化并发送 char *dest; osip_message_to_str(invite, &dest, NULL); sendto(sock, dest, strlen(dest), 0, ...); osip_free(dest); osip_message_free(invite);

这里osip_message_set_from的参数是字符串,Osip 内部会解析成osip_from_t。如果你要带 display name,写成"Alice" <sip:alice@example.com>。branch参数必须唯一,否则对端可能当成重传丢弃。osip_message_to_str分配的内存要用osip_free释放,不能用free,因为 Osip 可能用了自己的内存池。发送时sendto的目标地址就是你在sipSendDemo里指定的 IP 和端口。

3.3 把收和发拼成一个最小 UAC

单独跑sipRecvDemo和sipSendDemo只能看单向。要验证完整流程,你可以把两者的代码合到一个进程里:先起一个线程收包,主线程发 INVITE,收到响应后打印状态码。常见做法是用select或poll同时监听 socket 和标准输入,但 demo 阶段用两个线程更省事。

// 收包线程函数(简化) void *recv_thread(void *arg) { int sock = *(int *)arg; char buf[4096]; while (1) { int len = recvfrom(sock, buf, sizeof(buf), 0, ...); if (len > 0) { osip_message_t *msg; osip_message_init(&msg); if (osip_message_parse(msg, buf, len) == 0) { // 打印状态码或方法名 if (OSIP_IS_RESPONSE(msg)) { printf("status: %d\n", msg->status_code); } } osip_message_free(msg); } } return NULL; }

这段代码里,OSIP_IS_RESPONSE是 Osip 提供的宏,用来判断收到的是请求还是响应。响应的状态码在msg->status_code里,比如 100、180、200、401、407。如果你收到 401 或 407,说明对端要求认证,demo 阶段可以先不管,或者手动在 INVITE 里加 Authorization 头。注意recvfrom的最后一个参数要传struct sockaddr_in的指针和长度指针,别传 NULL,否则拿不到对端地址。

4. 避坑排查:Osip demo 跑不通的五个常见原因

4.1 编译报错找不到 osip.h

现象:fatal error: osip/osip.h: No such file or directory。原因:头文件路径没加进CFLAGS,或者 Osip 没装到系统目录。解决:在Makefile里加-I/path/to/osip/include,链接时加-L/path/to/osip/lib -losip2 -losipparser2。如果用的是自己编译的 Osip,注意make install的 prefix 是不是/usr/local,默认头文件在/usr/local/include/osip/。

4.2 收不到消息但抓包能看到

现象:sipRecvDemo启动了,tcpdump 也看到 UDP 包进来了,但程序没打印。原因:绑定地址不对,或者防火墙拦了。解决:先确认-i参数是不是0.0.0.0,如果绑的是127.0.0.1,外部包进不来。再检查iptables -L -n有没有 DROP 规则。还有一种可能是缓冲区太小,包被截断了,recvfrom返回的 len 等于缓冲区大小,这时候解析会失败,但程序可能没打印错误。

4.3 发出去的 INVITE 对端不回

现象:sipSendDemo显示发送成功,但抓包看不到响应。原因:Via 头里的 branch 不唯一,或者目标端口不对。解决:每次发 INVITE 前重新生成 branch,常见做法是用osip_build_random_number或直接拼时间戳。目标端口确认是 5060 还是对端实际监听的端口。如果对端是 Kamailio 或 Asterisk,检查它们的 SIP 监听配置。

4.4 解析大 SDP 时崩溃

现象:收到带长 SDP 的 INVITE,osip_message_parse返回非 0,或者程序直接 segfault。原因:接收缓冲区太小,SDP 被截断,解析器读到不完整字符串。解决:把char buf[4096]改成char buf[16384]或更大。同时检查osip_message_parse的返回值,别忽略错误码。如果 SDP 里有非 ASCII 字符,确认编码是 UTF-8。

4.5 内存泄漏导致跑几小时就 OOM

现象:sipRecvDemo跑一段时间后内存占用持续上升,最后被 OOM killer 杀掉。原因:osip_message_free没调用,或者osip_message_to_str分配的内存没释放。解决:每收一条消息,解析完必须osip_message_free。每发一条消息,osip_message_to_str出来的字符串用osip_free释放。用 valgrind 跑一遍valgrind --leak-check=full ./sipRecvDemo,看 definitely lost 的块。

5. 进阶技巧:用 demo 验证 SIP 事务重传与超时

5.1 手动触发重传看 Osip 的定时器行为

Osip 的 demo 默认不实现事务层重传,但你可以改代码,在发送 INVITE 后启动一个定时器,如果 500ms 内没收到 100 Trying,就重发一次。这能帮你理解 SIP 事务的 T1、T2、T4 定时器。常见做法是用osip_gettimeofday记录发送时间,在收包线程里检查是否超时。

// 简易重传逻辑(伪代码) time_t last_send = time(NULL); while (1) { if (time(NULL) - last_send > 1) { // 重发 INVITE sendto(sock, invite_str, strlen(invite_str), 0, ...); last_send = time(NULL); } // 收包处理... }

这里 1 秒是简化值,真实 SIP 事务的 T1 默认 500ms,每重传一次翻倍,直到 T2(4秒)。你可以把重传间隔打印出来,对照 RFC 3261 的定时器表看。注意重传时 Call-ID 和 branch 不能变,否则对端会当成新事务。

5.2 用 OPTIONS 做保活探测

sipSendDemo发 OPTIONS 是最简单的保活方式。你可以写个循环,每 30 秒发一次 OPTIONS,收到 200 OK 就认为链路正常。这比发 INVITE 轻量,不会建立对话。常见做法是把目标地址和端口做成命令行参数,循环间隔做成可配置。

参数含义建议值
发送间隔两次 OPTIONS 之间的秒数30
超时时间等待响应的秒数5
重试次数超时后重发几次3
目标端口对端 SIP 监听端口5060

这张表里的值是我在测试环境常用的,生产环境要根据网络质量调整。如果丢包严重,间隔可以拉到 60 秒,超时放到 10 秒。重试次数别太多,否则对端挂了你会一直发。

5.3 从 demo 到生产代码要补什么

demo 只演示了消息收发,生产代码至少还要补:事务层状态机、认证(401/407 处理)、DNS SRV 查询、NAT 穿透(rport、received 参数)、TLS 传输。Osip 本身提供了事务层 API(osip_transaction_t),但 demo 里没用。你可以从osip_transaction_init开始,把 INVITE 交给事务层管理,这样重传和超时就不用自己写了。

我自己的习惯是:每次改完 demo 代码,先用tcpdump -i any -n port 5060 -w sip.pcap抓包,再用 Wireshark 打开看 SIP 流程。Wireshark 的 SIP 解析器能直接标出重传、错误响应和 SDP 内容。如果 Wireshark 显示Malformed Packet,多半是 Content-Length 和实际 body 长度不一致,去代码里检查osip_message_set_content_length有没有设对。

从那以后我每次跑 Osip demo,都强制先抓包再下结论,因为屏幕打印会骗人,字节流不会。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 22:04:10

settings.json 里的六个 Slot 与四个视图怎么配

settings.json 里的六个 Slot 与四个视图怎么配 Martty 的配置面比命令表更易记混&#xff1a;一部分在 $MARTTY_HOME/settings.json&#xff0c;一部分只能通过 TUI 视图切换&#xff0c;还有启动参数。本文只讲「写在哪里、默认什么、何时生效」。版本口径 martty0.3.3、nod…

作者头像 李华
网站建设 2026/10/18 22:01:57

CSS 3D实战:构建立体旋转相册的核心原理与性能优化

做前端的人&#xff0c;多半都接到过“让页面有点高级感”这种需求&#xff0c;而照片展示是最容易出效果、也最容易翻车的场景之一。前阵子某公司的活动页需要把几十张现场照片做成“3D立体旋转相册”&#xff0c;挂在页面中段当视觉亮点&#xff0c;要求既能自动旋转&#xf…

作者头像 李华
网站建设 2026/10/18 22:01:25

ASP.NET Core Minimal API 全链路实践:服务端搭建与客户端调用

简介&#xff1a;本资源是一套完整的WebApi服务开发与调用实践项目&#xff0c;面向.NET初学者及中级开发者&#xff0c;聚焦RESTful接口设计、服务端实现与客户端通信两大核心能力。项目包含WebApi服务端&#xff08;含控制器、路由、模型绑定&#xff09;与独立访问客户端&am…

作者头像 李华
网站建设 2026/10/18 22:00:16

信贷风控实战:从特征工程到模型调优的贷款违约预测指南

简介&#xff1a;面向金融科技与信贷风控场景的机器学习实践源码&#xff0c;聚焦贷款违约风险预测&#xff0c;适用于金融机构信用评估、数据分析与建模学习者。压缩包共23个文件&#xff0c;约13.77MB&#xff0c;包含7个Python源码、2个CSV训练测试数据集、11个PNG图表&…

作者头像 李华
网站建设 2026/10/18 22:00:15

CSS hover父子联动原理与高性能实践指南

1. 项目概述&#xff1a;hover不是“悬停”那么简单&#xff0c;它是CSS中少有的父子联动开关你有没有试过&#xff0c;鼠标移到一个导航栏的菜单项上&#xff0c;不仅这个菜单项变色&#xff0c;它下面的二级下拉菜单也跟着展开&#xff1f;或者把鼠标移到一张商品卡片上&…

作者头像 李华