SIP协议

13 阅读14分钟

第 1 章:SIP 协议——电话世界的 HTTP

本章解决的痛点:抓包能看到 INVITE 消息,但为什么没声音?通话 30 秒后自动断开是为什么?理解 SIP 信令与 RTP 媒体的分离,是排查一切通话故障的起点。


1.1 为什么需要 SIP?从 PSTN 到 VoIP 的演进

1.1.1 电路交换的困局

在传统 PSTN(公共交换电话网络)时代,每一次通话都需要建立一条专属的物理电路。你拿起电话拨号,运营商的交换机就在两地之间拉起一根"虚拟专线",通话期间这条线路被独占,即使你们双方都在沉默,线路也无法被他人使用。

这种模式的问题很明显:

  • 资源浪费:语音数据有天然的静默期(一人说话时另一人在听),但电路一直被占用
  • 扩容成本高昂:每增加一路通话就需要物理线路,跨洋电话费用居高不下
  • 灵活性差:只能传语音,无法承载视频、即时消息等多媒体业务

1.1.2 分组交换的崛起与 H.323 的失败

20 世纪 90 年代,互联网兴起,人们开始思考:能不能像传文件一样传语音?将语音数据切分成小包,通过网络"尽力而为"地传输,这就是 VoIP(Voice over IP)的核心思想。

最早的 VoIP 协议是 H.323,由 ITU(国际电信联盟)制定。它过于复杂——完整的协议栈包含数十个配套协议,实现一个 H.323 终端需要处理复杂的 ASN.1 编解码和状态机。这导致 H.323 设备价格昂贵、互操作性差,最终在市场竞争中被淘汰。

1.1.3 SIP 的胜出:简单即正义

1996 年,IETF(互联网工程任务组)推出了 SIP(Session Initiation Protocol) ,RFC 3264 定义了它的核心规范。SIP 的设计哲学与 HTTP 一脉相承:

特性HTTPSIP
文本格式是,人类可读是,人类可读
请求-响应模型GET/POST → 200 OKINVITE → 200 OK
无状态/有状态无状态有状态(事务层)
可扩展头域

SIP 只负责"建立、修改、终止会话",不关心媒体如何传输。这种极简的分层设计让它迅速成为 VoIP 的事实标准。

实战对比

# HTTP 请求
GET /index.html HTTP/1.1
Host: example.com

# SIP 请求
INVITE sip:bob@example.com SIP/2.0
Via: SIP/2.0/UDP pc33.example.com;branch=z9hG4bK776asdhds
Max-Forwards: 70
To: Bob <sip:bob@example.com>
From: Alice <sip:alice@example.com>;tag=1928301774
Call-ID: a84b4c76e66710@pc33.example.com
CSeq: 314159 INVITE
Contact: <sip:alice@pc33.example.com>
Content-Type: application/sdp
Content-Length: 142

# SDP 内容(描述媒体参数)
v=0
o=alice 2890844526 2890844526 IN IP4 pc33.example.com
s=-
c=IN IP4 pc33.example.com
t=0 0
m=audio 49172 RTP/AVP 0
a=rtpmap:0 PCMU/8000

1.1.4 信令与媒体的分离——理解 VoIP 的第一性原理

这是本章最重要的概念,也是 90% 新手困惑的根源:

SIP 是信令协议(Signaling),负责"打电话这个动作";RTP 是媒体协议(Media),负责"传输语音内容"。两者独立运行,可能走不同的网络路径。

类比现实:

  • 信令 = 你拨号、对方振铃、对方接听、通话结束挂机的"动作序列"
  • 媒体 = 你们实际说话的声音

在 PSTN 时代,信令和媒体走在同一条电路上。但在 VoIP 中:

  • SIP 消息通常走 UDP 5060 端口(或 TCP/TLS)
  • RTP 媒体流走 UDP 10000-20000 等高端口范围

这就是为什么你能抓到 INVITE 但听不到声音

  1. SIP 通道通了(防火墙放行了 5060)
  2. 但 RTP 通道被防火墙/NAT 阻断(高端口未放行)
  3. 或者 SDP 中的 IP 地址是内网地址,对端无法直接发送 RTP 到正确的公网地址

这是贯穿全书的核心认知:排查故障时,先查信令(SIP),再查媒体(RTP)。


1.2 SIP 协议栈全景:UAC、UAS、Proxy、B2BUA

1.2.1 角色定义

SIP 网络中有四种核心角色:

角色全称职责类比
UACUser Agent Client发起 SIP 请求浏览器发送 HTTP 请求
UASUser Agent Server接收 SIP 请求并响应Web 服务器返回页面
ProxyProxy Server转发 SIP 消息,路由决策HTTP 代理/负载均衡
B2BUABack-to-Back User Agent作为中间人分别与两边建立对话NAT 网关、呼叫中心

1.2.2 FreeSWITCH 为什么是 B2BUA?

这是理解 FreeSWITCH 架构的关键。

传统 SIP Proxy 只是"传话筒":

Alice ──INVITE──→ Proxy ──INVITE──→ Bob
  ↑                                    ↓
  └────────────200 OK──────────────────┘

Proxy 不修改消息体,只根据路由规则转发。通话建立后,RTP 直接在 Alice 和 Bob 之间传输(如果网络允许)。

FreeSWITCH 作为 B2BUA

Alice ──INVITE──→ FreeSWITCH ──INVITE──→ Bob
  │                    │                    │
  │                    │                    │
  └────RTP 流─────────┘                    │
                       └────────RTP 流─────┘

FreeSWITCH 与 Alice 建立一段对话(Leg A),与 Bob 建立另一段对话(Leg B)。两边看到的 SIP 消息都是 FS 发出的,FS 可以完全控制两边的参数。

B2BUA 的优势

  • 媒体控制:可以录音、转码、混音(会议)
  • 拓扑隐藏:内网拓扑不暴露给外网
  • 协议转换:SIP ↔ WebRTC、UDP ↔ TCP/TLS
  • 增值业务:IVR、队列、呼叫转移

代价:所有媒体都经过 FS,对服务器性能要求高。


1.3 请求消息深度解析

SIP 定义了 14 种请求方法,最常用的是以下 6 种:

1.3.1 INVITE——发起会话的"敲门"

INVITE 是最复杂的 SIP 消息,它触发一次完整的呼叫建立流程。

消息结构解剖

INVITE sip:bob@example.com SIP/2.0  ← Start-Line(方法 + URI + 版本)
Via: SIP/2.0/UDP pc33.example.com:5060;branch=z9hG4bK776asdhds  ← 路径记录
Max-Forwards: 70  ← 防环路,每经过一跳减 1
To: Bob <sip:bob@example.com>  ← 逻辑接收者(显示名 + URI)
From: Alice <sip:alice@example.com>;tag=1928301774  ← 逻辑发送者 + 标签
Call-ID: a84b4c76e66710@pc33.example.com  ← 唯一标识一次通话
CSeq: 314159 INVITE  ← 序列号,用于消息排序和重传检测
Contact: <sip:alice@pc33.example.com>  ← 直接联系地址(绕过 Proxy)
User-Agent: FreeSWITCH-mod_sofia/1.10.10  ← 软件标识
Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE  ← 支持的方法
Supported: timer, path, replaces  ← 支持的扩展
Content-Type: application/sdp  ← 消息体类型
Content-Length: 142  ← 消息体字节数

[空行]

v=0  ← SDP 协议版本
o=alice 2890844526 2890844526 IN IP4 pc33.example.com  ← 会话所有者
s=-  ← 会话名(可选)
c=IN IP4 pc33.example.com  ← 连接信息(IP 地址)
t=0 0  ← 时间(0 0 表示永久)
m=audio 49172 RTP/AVP 0  ← 媒体描述:音频,端口 49172,RTP,编码 0
a=rtpmap:0 PCMU/8000  ← 属性:编码 0 是 PCMU,采样率 8kHz

关键头域详解

Via:记录消息经过的路径,用于响应路由。每经过一个 Proxy,会在顶部添加一个新的 Via。branch 参数是事务唯一标识,必须以 z9hG4bK 开头(表示符合 RFC 3261)。

From/To:逻辑上的发送者和接收者。注意 To 中没有 tag 参数——这是被叫方在响应中添加的。From tag + To tag + Call-ID 三者共同唯一标识一个 SIP 对话(Dialog)。

Call-ID:全局唯一标识一次通话。通常格式为 随机数@主机名

CSeq:命令序列号,同一个 Call-ID 内递增。用于检测丢包、乱序和重传。INVITE 的 CSeq 必须是递增的偶数(或任意数,但重传 INVITE 不改变 CSeq)。

Contact:告诉对方"直接联系我这个地址",后续请求(如 BYE)可以直接发到 Contact URI,而不必经过 Proxy。

1.3.2 ACK——确认收到最终响应

INVITE 是唯一需要三次握手的 SIP 方法:

Alice ──INVITE────────→ Bob
Alice ←──100 Trying───── Bob  (临时响应,表示收到请求)
Alice ←──180 Ringing──── Bob  (临时响应,表示正在振铃)
Alice ←──200 OK───────── Bob  (最终成功响应)
Alice ──ACK────────────→ Bob  (确认收到 200 OK)

为什么需要 ACK?因为 SIP 通常跑在不可靠的 UDP 上,200 OK 可能丢失。如果没有 ACK,Bob 不知道 Alice 是否收到了成功响应,会不断重发 200 OK。

重要区别

  • 针对 1xx 临时响应(100/180/183),不需要 ACK
  • 针对 2xx 最终响应(200),必须发送 ACK
  • 针对 3xx/4xx/5xx/6xx 错误响应,也需要 ACK

1.3.3 BYE——优雅地挂断

通话任何一方想结束,发送 BYE:

BYE sip:bob@192.168.1.2:5060 SIP/2.0
Via: SIP/2.0/UDP pc33.example.com:5060;branch=z9hG4bK77ef4c2312983.1
To: Bob <sip:bob@example.com>;tag=a6c85cf
From: Alice <sip:alice@example.com>;tag=1928301774
Call-ID: a84b4c76e66710@pc33.example.com
CSeq: 231 BYE
Content-Length: 0

对方回复 200 OK 确认挂断。

与 CANCEL 的区别

  • CANCEL:用于取消尚未建立的通话(在收到 200 OK 之前)
  • BYE:用于终止已经建立的通话(在收到 200 OK 之后)

1.3.4 REGISTER——告诉服务器"我在哪"

SIP 使用 URI(如 sip:alice@example.com)标识用户,但 URI 不直接对应 IP 地址。REGISTER 用于将 AOR(Address of Record,记录地址)绑定到当前 Contact 地址:

REGISTER sip:example.com SIP/2.0
Via: SIP/2.0/UDP 10.0.0.1:5060;branch=z9hG4bKna9a8s
To: Alice <sip:alice@example.com>
From: Alice <sip:alice@example.com>;tag=4fa2972e
Call-ID: nGFT9Mbxdf@10.0.0.1
CSeq: 1 REGISTER
Contact: <sip:alice@10.0.0.1:5060>;expires=3600
Content-Length: 0

关键参数

  • expires=3600:注册有效期 3600 秒(1 小时),之后需要重新注册
  • 服务器会返回 200 OK,包含当前绑定的 Contact 列表

在 FreeSWITCH 中,注册信息存储在 sofia_reg_database 或外部数据库中,可通过 sofia status profile internal reg 查看。


1.4 响应消息与事务状态机

1.4.1 状态码分类

SIP 响应状态码与 HTTP 类似,分为 6 类:

类别范围含义示例
1xx100-199临时响应(Informational)100 Trying, 180 Ringing
2xx200-299成功(Success)200 OK
3xx300-399重定向(Redirection)302 Moved Temporarily
4xx400-499客户端错误(Client Error)404 Not Found, 486 Busy Here
5xx500-599服务器错误(Server Error)500 Server Internal Error
6xx600-699全局错误(Global Failure)603 Decline, 604 Does Not Exist

1.4.2 常见状态码实战解读

100 Trying:Proxy/UAS 已收到请求,正在处理。这是可选的,用于防止客户端重传。

180 Ringing:被叫正在振铃。通常触发主叫端的回铃音。

183 Session Progress:带媒体的早期会话。可以在接通前播放彩铃或语音提示。

200 OK:请求成功。对于 INVITE,表示对方已接听,消息体包含最终的 SDP 答案。

302 Moved Temporarily:重定向。常用于呼叫转移:

SIP/2.0 302 Moved Temporarily
Via: SIP/2.0/UDP proxy.example.com;branch=z9hG4bK742b49
To: Bob <sip:bob@example.com>;tag=2c236de9
From: Alice <sip:alice@example.com>;tag=9fxced76sl
Call-ID: 3848276298220188511@alice.example.com
CSeq: 1 INVITE
Contact: <sip:bob@mobile.bob.com:5060>
Content-Length: 0

Alice 收到 302 后,应向新的 Contact 地址(bob@mobile.bob.com)重新发起 INVITE。

401 Unauthorized / 407 Proxy Authentication Required:需要认证。常见于注册或呼叫时未提供正确密码。

404 Not Found:URI 对应用户不存在。检查用户名拼写或 Domain 配置。

486 Busy Here:被叫忙。可能是对方正在通话,或策略拒绝。

488 Not Acceptable Here:SDP 协商失败。双方没有共同的编解码器,或媒体参数不兼容。

500 Server Internal Error:服务器内部错误。检查 FreeSWITCH 日志 freeswitch.log

503 Service Unavailable:服务不可用。可能是后端网关故障,或达到并发限制。

1.4.3 事务(Transaction)与会话(Session)的区别

这是 SIP 协议的核心概念,也是理解超时机制的钥匙:

事务(Transaction) :一个请求及其所有响应的集合,由 Via branch + CSeq + Call-ID 唯一标识。

事务有明确的状态机:

Calling ──→ Proceeding ──→ Completed ──→ Terminated
 (INVITE已发)  (收到1xx)      (收到2xx-6xx)    (计时器超时)

会话(Session) :两个 UA 之间的长期关联,由 From tag + To tag + Call-ID 唯一标识。会话从 INVITE 的 200 OK 开始,到 BYE 结束。

关键区别

  • 事务超时(如 32 秒无响应)会触发重传或失败
  • 会话超时(如 RTP 超时)会触发自动挂断

这就是为什么会有"30 秒掉线"问题:

  • SIP 会话实际还在(信令通道正常)
  • 但 RTP 媒体流中断(NAT 超时、防火墙阻断)
  • FreeSWITCH 的 rtp-timeout 参数(默认 60 秒)检测到无媒体后,发送 BYE 挂断

1.5 对话(Dialog)与会话的生命周期

1.5.1 Dialog 的建立

SIP Dialog 在收到 200 OK(针对初始 INVITE)时正式建立。此时:

  • 双方都知道对方的 Contact 地址
  • 双方都分配了 RTP 端口
  • 可以双向发送 BYE 终止通话

Dialog 建立后,后续请求(如 BYE、Re-INVITE)可以直接发送到 Contact 地址,路径更短:

# 初始 INVITE 经过多个 Proxy
Alice → Proxy1 → Proxy2 → Bob

# Dialog 建立后的 BYE 直接发送
Alice → Bob

1.5.2 Re-INVITE 与会话修改

通话建立后,任何一方可以发送新的 INVITE(称为 Re-INVITE)来修改会话参数:

  • 保持(Hold):a=sendonlya=inactive
  • 恢复:a=sendrecv
  • 转码协商:添加/删除编解码器
  • 更新 RTP IP/端口(NAT 场景)

1.5.3 僵尸通话(Orphan Call)的产生与清理

什么是僵尸通话

  • 网络异常导致 BYE 消息丢失
  • 一方认为通话已结束,另一方仍在等待
  • 持续占用系统资源(端口、内存、计费)

检测机制

  1. SIP 层面:FreeSWITCH 的 session-timeout(默认 1800 秒),超时无消息发送 OPTIONS 检测
  2. RTP 层面rtp-timeout(默认 60 秒),超时无媒体流则挂断
  3. 应用层面:自定义心跳(如 WebSocket keepalive)

实战配置(conf/sip_profiles/internal.xml):

<!-- RTP 超时:60秒无媒体则挂断 -->
<param name="rtp-timeout" value="60"/>
<param name="rtp-hold-timeout" value="1800"/>

<!-- 会话超时:30分钟发送 re-INVITE 刷新 -->
<param name="session-timeout" value="1800"/>
<param name="minimum-session-expires" value="90"/>

实战:用 sngrep 抓包分析一次完整通话

环境准备

# 安装 sngrep(Debian/Ubuntu)
apt-get install sngrep

# 启动实时捕获(需要 root)
sudo sngrep -d eth0

# 或分析 pcap 文件
sngrep -r capture.pcap

抓包场景:Alice (101) 呼叫 Bob (102)

步骤 1:注册(REGISTER)

101 ──REGISTER──→ FreeSWITCH
101 ←──200 OK───── FreeSWITCH (expires: 3600)

观察点:Contact 头中的 IP 是内网还是公网?如果是内网,后续需要考虑 NAT 穿越。

步骤 2:发起呼叫(INVITE → 200 OK → ACK)

Alice ──INVITE──→ FS ──INVITE──→ Bob
Alice ←─100 Try── FS ←─100 Try── Bob
Alice ←─180 Ring─ FS ←─180 Ring─ Bob
Alice ←─200 OK─── FS ←─200 OK─── Bob
Alice ──ACK────── FS ──ACK──────→ Bob

观察点:

  • SDP 中的 c= 行(连接地址)和 m= 行(媒体端口)
  • 双方协商的编解码器(RTPmap)

步骤 3:媒体传输(RTP) 此时打开 Wireshark,过滤 rtp,可以看到双向的 RTP 流:

  • 源端口/目标端口
  • 序列号(Sequence Number)连续性
  • 时间戳(Timestamp)递增

步骤 4:挂断(BYE → 200 OK)

Alice ──BYE────── FS ──BYE──────→ Bob
Alice ←─200 OK─── FS ←─200 OK─── Bob

故障排查速查表

现象排查点工具
INVITE 无响应网络连通性、防火墙 5060 端口ping, telnet
401/407 错误认证信息(username/password)fs_cli, sngrep
488 Not Acceptable编解码器不匹配sngrep 查看 SDP
单通(一方无声音)RTP 方向、NAT、防火墙sngrep, tcpdump
30-60 秒掉线RTP 超时、NAT 保活fs_cli show calls
180 Ringing 但无回铃音183 Early Media、SDP 问题sngrep

本章小结

  1. SIP 是信令,RTP 是媒体,两者分离是 VoIP 的核心特征。看到 INVITE 只是第一步,确保 RTP 连通才有声音。
  2. FreeSWITCH 是 B2BUA,它与每一方分别建立对话。这带来了灵活性(录音、转码),也要求更高的服务器性能。
  3. Via/From/To/Call-ID/CSeq 是消息路由的五要素,理解它们的含义是阅读 SIP 消息的基础。
  4. 事务(Transaction)与会话(Session)有独立的状态机,理解超时机制能解释"为什么 30 秒掉线"。
  5. sngrep 是 SIP 排查的首选工具,学会用它抓包分析,胜过背诵所有配置参数。

练习题

  1. 为什么 INVITE 需要 ACK 确认,而 REGISTER 不需要?
  2. 抓包时发现 Call-ID 相同但 CSeq 不同,这是什么场景?
  3. 被叫收到 INVITE 后立即回复 486 Busy,后续的 ACK 由谁发送?
  4. 在 NAT 环境下,SDP 中的 c=IN IP4 192.168.1.100 会导致什么问题?如何解决?

延伸阅读

  • RFC 3261: SIP: Session Initiation Protocol(核心规范)
  • RFC 3264: An Offer/Answer Model with the Session Description Protocol (SDP)
  • FreeSWITCH 官方文档: developer.signalwire.com/freeswitch/

下一章预告:SDP——媒体协商的艺术。我们将深入解析 offer/answer 模型,理解编解码器协商、hold/resume 的 SDP 实现,以及带宽管理的策略。