写 SIP 服务后台前,先把 SIP 和 PSTN 搞清楚

1. 写在最前面

最近准备去写一个和 SIP 相关的后台服务。

最开始笔者以为,这件事的重点应该是先把协议文档看懂,把 REGISTERINVITEBYE 这些流程背下来。结果真正开始查资料之后,发现自己最先卡住的,反而不是某个字段,而是几个更基础的问题:

  • SIP 到底是不是「打电话」本身?
  • PSTN 到底是什么?
  • 为什么有时候电话明明接通了,却没有声音?
  • 为什么分机互打和打手机号,系统里出现的东西完全不一样?

如果这些问题没想清楚,后面越看资料越容易乱。因为脑子里会一直把很多东西糊成一团,最后只剩下一个很模糊的印象:反正都是打电话相关的东西。

但真到要写后台的时候,这种模糊理解基本不够用。

因为你很快就会碰到这种很实际的问题:

  • 这通电话到底是建立失败了,还是声音没传过去?
  • 分机号和手机号为什么不能按同一套逻辑处理?
  • 日志里到底该看什么,才能把一通电话串起来?

所以这篇文章不打算上来就讲协议,而是想先把问题拆清楚。对现在的笔者来说,真正重要的不是先把术语背熟,而是先知道一通电话到底可以从哪几个层面去看。

2. 为什么 SIP 和 PSTN 很容易被搞混

现在回头看,笔者觉得自己一开始会被绕晕,其实是因为平时说「打一通电话」时,默认把很多事情都当成了一个动作。

但如果站在后台视角去看,这件事至少可以拆成三层:

  1. 这通电话怎么建立起来
  2. 声音怎么传过去
  3. 如果目标是手机号,这通电话最后要落到哪里

一旦不先拆开,后面就很容易把下面这些东西混在一起:

  • SIP
  • RTP
  • PSTN
  • 外部中继
  • 网关

所以现在如果让我先给自己留一个最小理解,我会先这么记:

复制代码
SIP 管的是「这通电话怎么建立」
RTP 管的是「声音怎么传」
PSTN 是「真实电话号码所在的电话网络」

这句话并不够完整,但已经足够把很多混乱先压下来了。

3. 先看两通最普通的电话

如果不先看具体场景,后面的概念很容易飘。

所以笔者现在更愿意只看两通最普通的电话。

3.1 第一通:10011002

假设这是两个内部分机。

那这通电话,先不用带太多术语去看,可以先理解成这样:

markdown 复制代码
1. 1001 先告诉服务端「我在线」
2. 1001 发起呼叫,目标是 1002
3. 1002 开始响铃
4. 1002 接听
5. 双方开始说话
6. 某一方挂断

这个场景里,笔者觉得最值得先记住的不是报文名,而是两个问题:

  • 这通电话现在建立到哪一步了?
  • 声音现在有没有正常传起来?

因为你后面看到的 REGISTERINVITE200 OKBYE,本质上都只是在描述第一件事:这通电话走到哪一步了。

也就是说,在这个场景里,系统主要还是在处理一条内部呼叫链路。

3.2 第二通:1001138xxxx8888

如果 1001 打的不是 1002,而是一个普通手机号,那事情就不一样了。

因为这时候这通电话不能只在系统内部打转,它最后一定要落到真实电话号码那边去。

链路大概会变成这样:

rust 复制代码
1001 -> 你的 SIP 服务 -> 外部语音出口 -> PSTN -> 手机用户

到这里,PSTN 这个词才真正有了位置。

它不是一个和 SIP 并排的协议名,而是电话最后要去到的那张网。

所以如果要把这两通电话的差别说得再直白一点,那大概就是:

  • 分机互打,更多是在系统内部把一通呼叫接起来
  • 打手机号,除了内部呼叫控制,还多了一段「把电话送进真实电话网络」的过程

这一步一旦想清楚,后面为什么会出现外部中继、网关、运营商线路,其实就没那么难理解了。

4. SIP 到底在这两通电话里管什么

写到这里,再回头看 SIP,就会比一上来背定义更清楚一点。

SIP 最核心的作用,并不是「替你把声音传过去」,而是负责让一通电话从开始到结束有状态可跟。

更直白一点说,它管的是这些事:

  • 谁要呼叫谁
  • 对方是不是在响铃
  • 对方有没有接起来
  • 这通电话是不是已经挂断

所以对写后台的人来说,SIP 最重要的价值,不是它有多像协议,而是它能把一通电话的状态组织起来。

这也是为什么我现在越来越觉得,SIP 后台更像一个呼叫状态机,而不只是一个「收发报文的服务」。

因为你真正要维护的,往往不是某一条消息,而是:

  • 谁已经注册
  • 谁正在呼叫谁
  • 这通电话处于振铃、接通,还是已经结束
  • 失败到底发生在什么阶段

如果从工程视角去看,这一层其实就是后面业务控制的基础。

5. 为什么会出现「接通了,但没声音」

这个问题,笔者一开始是真的很疑惑。

既然电话都接通了,为什么还会没声音?

后来才慢慢意识到,原来「把电话接起来」和「把声音传过去」不是一回事。

在前面的两个例子里,其实一直都藏着两条线:

  • 一条线是呼叫有没有建立起来
  • 一条线是声音有没有真的传起来

前一条更多和 SIP 有关。

后一条通常更多和 RTP、媒体地址、网络连通性这些事情有关。

所以才会有一种特别经典的现象:

  • 看起来已经接通了
  • 双方都进入了通话状态
  • 但就是听不到声音

这时候往往不是呼叫建立失败了,而是媒体那条链路出了问题。

比如:

  • 地址给错了
  • 内网地址跑到了公网
  • NAT 没打通
  • 媒体没有经过该经过的节点

所以对写后台来说,一个特别重要的认知是:

「接通」和「有声音」其实是两件事。

这个点如果一开始没分清,后面排查问题时会非常容易误判。

6. 那些总是冒出来的词,到底该怎么记

当开始看资料以后,很快就会遇到这些词:

  • SBC
  • Gateway
  • SIP Trunk

一开始真的很容易看烦。

因为每多一个词,脑子里都像是又多了一个要背的概念。

但后面笔者发现,刚开始其实不用把它们记得太学术,先把它们放回上面第二通电话里去看,就够用了。

如果 1001 要打手机号,那系统中间往往会多出一些「负责出网」的角色。

我现在更愿意这么理解:

  • SIP Trunk:系统往外打电话时用的语音出口
  • Gateway:负责把系统这一侧接到传统电话网络
  • SBC:更像边界上的守门员,处理公网接入、安全、NAT 这些问题

只要把这些角色放回「1001 打手机号」那条链路里,基本就不会再觉得它们是凭空冒出来的词了。

7. 写 SIP 后台时,真正难的到底是什么

写到这里,笔者反而越来越觉得,写 SIP 后台时最重要的,不是上来就背一堆术语,而是先盯住几个最实际的问题:

  • 谁注册上来了
  • 这通呼叫现在在哪个阶段
  • 分机号和手机号要怎么分开走
  • 外呼的时候该走哪个出口
  • 出问题时是呼叫建立的问题,还是声音传输的问题

继续往下想,会发现真正难的地方,通常也不在于「把一条 INVITE 发出去」。

真正难的往往是:

  • 内线和外线怎么区分
  • 不同号码该走哪条出口
  • 某个出口失败后要不要切别的
  • 挂断、拒接、超时这些状态怎么统一表达

也就是说,真正难的地方,很多时候不是协议本身,而是:

  • 路由怎么做
  • 状态怎么管
  • 问题怎么定位

一旦开始从这个角度看,SIP 后台就会越来越像一个呼叫控制系统

8. 如果我是写后台的人,我到底该先盯住什么

写到这里,笔者会更愿意把注意力收回到开发本身。

如果后面真的开始写后台,那我最先会逼自己想清楚的,其实是下面几件事:

  • 注册信息怎么维护
  • 呼叫状态怎么表示
  • 内线和外线怎么区分
  • 打手机号时走哪个出口
  • 一通电话的日志怎么串起来

最后这一点尤其重要。

因为一通电话看着只是用户点了一下拨号,但到了后台里,往往会跨很多节点。

这时候如果没有一个稳定的标识,后面排查起来会非常痛苦。

所以后面你大概率会在日志里反复看到 Call-ID 这个字段。可以先把它粗暴理解成「这通电话自己的 id」。先知道这一点,后面串日志时会省很多劲。

所以现在回头看,写 SIP 后台真正要做的,其实不是把所有术语都背全,而是先把下面这几层分开看:

  • 呼叫有没有建立起来
  • 声音有没有真的传起来
  • 这通电话有没有走到真实电话网络

边界一旦清楚,后面很多设计和排查思路都会顺很多。

9. 碎碎念

写这篇之前,笔者其实最怕的是把自己也绕进去。因为 SIPRTPPSTN 这些词一旦一起出现,就很容易越看越像一团。

但现在至少有一件事是清楚的了:

先不要一上来就背协议,先把「呼叫建立」「声音传输」「电话落网」这三件事分开看。

分开以后,很多概念自然就有位置了。后面如果真的开始写后台,笔者会先把问题看清楚,再去补术语,而不是反过来。

  • 不能等到清晰了以后再去做,不去做就永远清晰不了,在做的过程中才会逐渐清晰起来的。
  • 我们不会长生不老,我越想到这个,越觉得不能浪费人生。因为生命仅此一次,任何事情,无论它是有趣的,好笑的,或是重要的,都仅此一次。你知道的,每天都有可能是我们的最后一天。

10. 参考资料

  • RFC 3261:SIP: Session Initiation Protocol
  • RFC 3550:RTP: A Transport Protocol for Real-Time Applications
  • RFC 4566:SDP: Session Description Protocol
  • ITU-T E.164:国际公用电信编号计划
相关推荐
码农BookSea2 小时前
为什么ChatGPT能听懂你说的话?Embedding技术揭秘
后端·openai
黑牛儿2 小时前
MySQL 索引实战详解:从创建到优化,彻底解决查询慢问题
服务器·数据库·后端·mysql
程序员飞哥2 小时前
到底Java 适不适合做 AI 呢?
后端·程序员·全栈
码事漫谈4 小时前
AI提效,到底能强到什么程度?
前端·后端
IT_陈寒4 小时前
React hooks依赖数组这个坑差点把我埋了
前端·人工智能·后端
RATi GORI4 小时前
springBoot连接远程Redis连接失败(已解决)
spring boot·redis·后端
阿祖zu4 小时前
内容创作 AI 透明化声明倡议与项目开源
前端·后端·github
糟糕好吃5 小时前
AI 全流程解析(LLM / Token / Context / RAG / Prompt / Tool / Skill / Agent)
前端·后端·设计模式
快手技术5 小时前
快手广告系统全面迈入生成式推荐时代!GR4AD:从Token到Revenue的全链路重构
前端·后端