📹 第九章 SIP及eXosip开源库应用
📄 一句话概括

一通电话一个对话、三个以上的事务、八条左右的报文—— 这个数量关系能帮你判断「哪里没清掉」。 「事务数只涨不跌」通常不是 bug,而是「对话没关掉」。

📝 本节和前面几节的关系

09.02.07 讲了事件,这一节讲事件背后那些要被回收的东西。 09.01.02 讲过事务与对话的分层,这一节用「数量」把它落到实处。

一、三个层次的数量

单位 一通电话 三通
对话 1 3
事务 3 以上 9 以上
报文 8 左右 24 左右

第二行那个「3 以上」是下限:INVITE、ACK、BYE 各一个, re-INVITE(换编码、保持会话)会再增加。 所以「事务数暴涨」通常不是 bug,而是「对话没关掉,事务也跟着不清」——两者是连着的。 第三行那个「8 左右」和 09.01.05 的实测吻合: 两轮注册 4 条(2 请求 2 应答)、呼叫 5 条、挂断 2 条。

二、谁负责回收

东西 谁回收 什么时候
eXosip_event_t 你 用完立刻
osip_message_t 你 build 之后用完
事务 库 事务结束后
对话 库 BYE 之后

前两行是「你自己建的、你自己放」,后两行是「库建的、库放」——这条界线不能混。 所以「手动 free 事务」是错的(09.02.05 讲过),而「不 free 事件」也是错的。 判据很简单:谁调用了 _init 或 _build,谁就负责 _free。 库内部建的东西,库自己会放。

三、对话没关掉会怎样

这是长跑程序最典型的内存问题:

情况 后果
BYE 丢了、没收到 200 对话一直不结束
对端异常断电 要等超时
自己的程序异常退出没清 重新注册时残留

第一行那个最常见:BYE 发出去了,但对端没回 200(网络抖了一下)。 库会按 Timer F 重传,重传到 32 秒还没回应,才算超时、才回收对话。 所以「挂断后对话立刻消失」是错的期待——它会存在最多 32 秒。 如果这时又发起同一路的新呼叫,服务端可能还有旧记录。 第二行那种「对端断电」更麻烦: 没有任何报文通知你,只能靠「RTP 收不到包」或者「注册续期失败」间接发现。

四、怎么观察有没有泄漏

两条实用办法:

办法 看什么 判据
打点日志 对话数、事务数 是否回到基线
压力测试 内存曲线 是否锯齿

第一行那个「打点」要选对位置:在「呼叫成功」和「挂断完成」两处各打一行, 看两个计数是不是同步增减。 如果只有增没有减,立刻就能定位。 第二行那个「锯齿」是关键判据:正常程序的内存应该「涨上去又落回来」; 如果每次涨一点、从不回落,就是泄漏。 注意要跑够久:每次泄漏几百字节的话,跑十分钟看不出来,跑一天才明显。

五、数量关系的实用价值

这三行数字不只是「知识」,它能当判据用:

现象 可能的原因
一通电话有 30 条报文 重传了
只有 4 条 某一步短路了
对话数远大于在线通道数 旧的没清

第一行那个「30 条」说明丢了包:每丢失一次就多一条重传, 而重传的报文内容完全一样(同一个 branch)——抓包看到重复的 branch 就是重传。 第二行那个「只有 4 条」说明流程没走完:可能注册失败就退出了,或者呼叫被拒(4xx)。 所以「数报文条数」是一个很快的初筛手段,比逐条读报文快得多。

六、动手 Demo:三通呼叫,事务与会话各几个

Demo 起三台 UAC,各自走一遍完整流程,把三通电话的标识摆在一起看。 如果三行的 Call-ID 或远端 tag 有重复,就说明标识生成有问题。

for i in (1, 2, 3):
    uac = S.UAC(uas_port)
    uac.register()
    codes = uac.invite()
    uac.ack()
    uac.bye()
    print(uac.port, uac.dialog_key())
    uac.close()

实跑输出:

--------------------------------------------------------------------
三通呼叫,事务与会话各几个
--------------------------------------------------------------------
**事务和对话的区别在 09.01.02 讲过**,**这一节用「数量」把它量化。**

  第几通     UAC 端口     Call-ID 前 10 位     远端 tag     应答
  ----------------------------------------------------------------------
  1          60489        52fd4fd201…          7a8c01cb     100+180+200
  2          60490        2825f55db2…          8e0ad327     100+180+200
  3          60491        69ef5fdfc1…          645e7562     100+180+200

**三行的 Call-ID 与远端 tag 全不同**——**这是「三通独立对话」的证据。**
**而它们共用一个 UAS 端口**,**因为服务端本来就要处理多路。**

**数量关系可以这样记**:

  单位     一通电话里几个     三通总共
  ----------------------------------------
  对话     **1**              **3**
  事务     **3 以上**         **9 以上**
  报文     **8 左右**         **24 左右**

**第二行那个「3 以上」是下限**:**INVITE、ACK、BYE 各一个**,
**re-INVITE 或者被叫再挂一次就更多。**
**所以「事务数暴涨」通常不是 bug,而是「对话没关掉」。**

**清理这件事必须自己做**:**`eXosip_event_free` 只释放事件本身**,
**事务和对话的记录要等「对话结束」才回收**。
**如果 BYE 丢了、对话一直不结束**,
**服务端就会一直占着这几条记录**——**这就是「长跑几天内存缓慢上涨」的常见原因。**

三行的 Call-ID 与远端 tag 全不同,这是「三通独立对话」的证据。 而这正是「标识必须唯一」的实际意义: 如果两通电话的 Call-ID 撞了,服务端会把它们当成同一通——表现是「挂断一路,另一路也断了」。 这个 bug 在测试时很难发现(因为只有一路),上了现场多路并发才暴露。 所以 Demo 里三通并排看,是刻意的:「唯一性」这种事情只有摆在一起才能验证。 注意三个 UAC 用的是同一个 UAS 端口,因为服务端本来就该处理多路; 而它们各自的本地端口不同,这是「同一台机器跑多个客户端」的正常状态。

30 秒实验

  1. 把三通呼叫的 Call-ID 手动改成一样,看 UAS 会不会混。
  2. 在 bye() 之前把 UAS 停掉,观察「对话没关掉」的表现。
  3. 跑 100 通呼叫,用 tasklist 或 ps 看内存涨不涨。

常见误区

  1. 以为挂断后对话立刻消失。 BYE 丢了要等超时才回收,最长 32 秒; 这期间服务端可能还有旧记录。
  2. 手动 free 事务。 事务是库建的、库放的,手动释放会打乱状态机。 判据是「谁 init 谁 free」。
  3. 只跑十分钟就判断没有内存泄漏。 每次几百字节的话要跑一天,短时间看不出来。
自测三问(点开对答案) 1. 一通电话里有几个对话、几个事务? —— **对话 1 个、事务 3 个以上**(INVITE、ACK、BYE),re-INVITE 会再增加。 2. 「事务数只涨不跌」最可能是什么原因? —— **对话没关掉**。事务跟着对话走,对话不结束事务也不回收,常见于 BYE 丢包后一直等超时。 3. 一通电话出现 30 条报文说明什么? —— **重传了**。每丢一次包就多一条重传,而重传报文内容相同、branch 也相同,抓包能直接看出来。

下一节收口:把 eXosip 的整张栈图讲清,第九章就完了。

相关笔记