注册是所有国标交互的起点:不注册,平台上就看不到这台设备,后面什么都干不了。 它的过程是「挑战—应答」:先发空的 REGISTER,平台回 401 带随机数,设备带摘要再发一次。
前面几节把「报文长什么样」讲完了,从这一节开始进流程——把一个个报文串成一条时间线。 注册是第一个流程,也是最该先调通的一个,它不通,后面全是空谈。
一、为什么第一次不带密码
第一次 REGISTER 是完全「裸」的:不带任何凭据,只有设备编号和有效期。
平台这时候会回 401,把随机数和域一起给回来。
设备拿到随机数,才算有了「挑战」的原料。
为什么这么设计:因为随机数必须是平台生成的,设备自己造的不作数,否则重放就没有门槛了。
所以「两个来回」是必需的,不是实现偷懒。这也解释了为什么注册比心跳慢——心跳不用挑战。
调试时常见的现象是「只看到第一个 REGISTER,没有第二个」,
那多半是设备没解出 401 里的 WWW-Authenticate 头,或者摘要算法算错,干脆不发了。
二、有效期和心跳的关系
Expires 是这次注册的有效期,常见取值是一小时。过期不重注册,平台就把设备当离线。
但设备不会等到过期才动:它靠心跳维持在线状态,注册只在需要时重做。
两者分工很清楚:注册说「我是谁、我能待多久」,心跳说「我还在」。
这个分工带来一个实际影响:平台判在线看的是心跳,不是注册记录。
所以「注册成功但在线状态是离线」这种矛盾,恰恰说明注册通了、心跳没通。
排障顺序因此很明确:先看注册有没有 200,再看心跳有没有到,最后才看点播。
三、注销与「意外离线」
注销就是把 Expires 写成 0,同一个 REGISTER,一个字都不多。
平台收到就把设备标离线、清会话。
但现实里大多数设备是「不告而别」的:断电、拔网线、死机,都不会发注销。
所以平台必须有第二条路径:心跳超时判离线。
这条路径比注销更重要,因为它覆盖了绝大多数情况。
两条路径的差别要在数据上体现出来:注销是「正常下线」,超时是「异常掉线」,
运维看的是后者。
如果两者在库里长得一样,就没法做可用性统计。
四、动手 Demo:一次注册来回四趟,报文都写了什么
按真实格式造四个报文:REGISTER、401、带摘要的 REGISTER、200 OK。 量各自的字节数,再看心跳的量级。
md5 = lambda s: hashlib.md5(s.encode("utf-8")).hexdigest()
ha1 = md5("%s:%s:%s" % (user, realm, pwd))
ha2 = md5("REGISTER:sip:%s" % realm)
resp = md5("%s:%s:%s:%s:%s:%s"
% (ha1, nonce, "00000001", "0a1b2c3d", "auth", ha2))
# 第二次注册 = 第一次 + Authorization 头 + CSeq 加一
auth = 'Authorization: Digest username="%s", realm="%s",' % (user, realm)
req2 = req1.replace("CSeq: 1 REGISTER", "CSeq: 2 REGISTER")
req2 = req2.replace("Expires: 3600", auth + "Expires: 3600")
print(len(req2) - len(req1))
实跑输出:
--------------------------------------------------------------------
一次注册来回四趟,报文都写了什么
--------------------------------------------------------------------
**注册是所有国标交互的起点**:**设备不注册,平台上就看不到它,什么都干不了。**
**过程是「挑战—应答」**:**先发一个空的 REGISTER,平台回 401 带随机数,设备带着摘要再发一次。**
步骤 长度 行数
-----------------------------------------------------------
**① REGISTER(无凭据)** **239 字节** **9 行**
**② 401 挑战** **114 字节** **4 行**
**③ REGISTER(带摘要)** **403 字节** **10 行**
**④ 200 OK** **52 字节** **4 行**
四次往返合计 808 字节
第三次和第一次的差 = 164 字节,正好是 Authorization 那一行
**注册一次不到一千字节,但要走两个来回**——
**因为第一趟是「试探」,不带凭据;第二趟才是「应答」,带摘要。**
**这个设计的好处是「设备不必预存随机数」**,**坏处是注册慢了一倍。**
**所以设备通常不会频繁重注册,而是靠心跳维持。**
**注册之后还要保持在线**:
Expires = 3600 秒,心跳周期 60 秒
一个有效期内要发 60 个心跳
千路设备每小时心跳 60000 个、约 14.4 MB
**心跳报文只有两百多字节,一千路加起来每小时十几兆**,
**所以它从来不是带宽问题,是「平台要一直醒着」的问题。**
**注销就简单了**:**同一个 REGISTER,把 `Expires` 写成 0**。
**平台收到就把设备标成离线,会话表清掉。**
**这里有个坑**:**设备突然断电是不会发注销的**,
**平台只能靠「心跳超时」判离线**——**所以「设备离线」和「设备注销」是两条不同的路径。**
四个报文加起来八百字节出头,
其中第二次 REGISTER 比第一次多了 164 字节——正好是 Authorization 那一整行。
这 164 字节就是「带凭据」的全部代价:
一个用户名、一个域、一个随机数、一个 URI、一个三十二位的哈希。
注意第一次是 239 字节、带凭据是 403,涨了将近七成——凭据本身比报文头还长。
心跳那组数说明一件事:一个有效期内六十个心跳、千路每小时六万个、十四兆出头。
所以心跳从来不是带宽问题,真正的问题是「平台要为每个心跳做多少事」。
30 秒实验
- 算一下:
Expires一小时、心跳六十秒,一个有效期内几个心跳?(60 个) - 想一下:为什么第一次 REGISTER 不带凭据?
- 想一下:「注册成功但在线状态是离线」说明什么?(心跳没通)
常见误区
- 以为「注册成功就等于在线」。 在线看心跳,注册只说明口令对。
- 以为「注销和掉线是一回事」。 一个是正常下线,一个是超时判死,运维统计要分开。
- 以为「注册一次就一劳永逸」。
Expires到期要重注册。
自测三问(点开对答案)
1. 注册为什么要两个来回? —— **因为随机数必须由平台生成下发**,**设备拿到之后才能算摘要。** 2. 平台靠什么判在线? —— **心跳**。注册只说明「口令对、有效期到什么时候」。 3. 注销怎么发? —— **同一个 REGISTER,`Expires` 写 0**。下一节进点播:一次 INVITE 到首个 RTP 包,中间隔了几趟。
- ☐ (本节完,下一节 10.08.02 实时视音频点播)