资讯动态

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

发布时间:2026/10/18 22:05:56 来源:尧图企业网站定制
简介本资源为基于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:aliceexample.com); osip_message_set_to(invite, sip:bobexample.com); osip_message_set_call_id(invite, 12345678901.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;branchz9hG4bK123); // 序列化并发送 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:aliceexample.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 的 INVITEosip_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-checkfull ./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每重传一次翻倍直到 T24秒。你可以把重传间隔打印出来对照 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 本身提供了事务层 APIosip_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都强制先抓包再下结论因为屏幕打印会骗人字节流不会。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑