news 2026/10/6 11:42:07

短信业务流程分析:从短信中心到计费系统的全链路拆解与状态机验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
短信业务流程分析:从短信中心到计费系统的全链路拆解与状态机验证

简介:这份《短信业务流程分析》PPT面向通信、嵌入式及管理信息化方向的开发与运维人员,系统梳理短信业务从存储转发机制到PDU编码落地的完整链路,适合需要理解短信收发原理、调试AT指令或优化短信服务系统的技术人员参考。资源包内含1个pptx文件,约1.16MB,以图文幻灯片形式组织内容,便于按章节浏览与课堂讲解。目前已有171人学习下载。内容覆盖SMS存储转发模式与SMSC转发机制、PDU与Text两种发送模式对比、7-bit/8-bit/UCS2三种编码方式,并整理AT+CMGC、AT+CMGF、AT+CMGS、AT+CSCA等常用AT指令的功能说明;同时拆解PDU格式的A至M各字段构成,配合“工作愉快!”实例演示短信中心号码与接收号码的奇偶位交换、国际化标志添加及长度计算等编码步骤,可帮助读者建立从协议原理到实际编码的完整认知,为短信模块开发与排错提供参考。

1. 短信业务流程分析:从一条短信的旅程看系统瓶颈在哪

一条短信从手机发出到对方收到,中间要穿过短信中心、信令网、网关、计费系统、SP平台,任何一环卡住,用户看到的就是“发送失败”或者延迟几分钟才到。很多人以为短信就是“发出去就完事”,但真正做过短信业务系统的人都知道,这里面的流程链路比大多数HTTP接口调用复杂得多。短信业务流程分析要解决的核心问题是:把这条链路上每个节点的处理逻辑、状态流转、异常分支全部拆开,找到延迟、丢失、重复的根因。适合谁看?做短信网关开发的、做SP业务后台的、做运营商侧接口对接的,以及需要排查短信收发异常但又拿不到底层日志的运维和测试人员。下面我按实际排查问题的思路,把这条链路从头到尾走一遍。

2. 短信从提交到送达的完整链路拆解

2.1 短信中心在链路里到底做了什么

短信中心是整条链路的枢纽。手机提交短信时,通过信令通道把消息发给短信中心,短信中心先做鉴权——检查这个号码有没有短信权限、是否欠费、是否被列入黑名单。鉴权通过后,短信中心把消息存下来,返回一个提交响应给手机,这时候手机界面上显示“已发送”。注意,这个“已发送”只代表短信中心收到了,不代表对方收到了。

接下来短信中心要查路由:接收方当前在哪个MSC(移动交换中心)下注册。如果接收方开机且在网络中,短信中心通过信令把消息推给接收方所在的MSC,MSC再通过基站发给手机。如果接收方关机或不在服务区,短信中心会把消息存起来,等接收方重新注册时再尝试投递。这个“存储转发”机制是短信可靠性的基础,但也是延迟的根源——状态报告回来之前,你根本不知道对方到底收没收到。

常见做法是:在短信中心侧配置重试策略,比如首次投递失败后间隔5分钟重试,最多重试3次,超过后标记为失败并生成状态报告。这个重试间隔和次数直接决定了用户感知到的延迟上限。

2.2 状态报告怎么回传,为什么经常对不上

状态报告是短信业务流程里最容易出问题的地方。短信中心投递成功后,会生成一个状态报告,通过信令回传给短信发起方。但实际场景中,状态报告经常丢失或者延迟。原因有几个:一是状态报告走的是和短信不同的信令通道,可能被限流;二是SP平台和短信中心之间的协议映射有问题,比如SMPP协议里的deliver_sm和submit_sm_resp的message_id对不上;三是跨运营商时,状态报告格式不兼容。

我一般会这样排查:先在SP平台侧记录每条短信的message_id和提交时间,然后在短信中心的日志里搜同一个message_id,看状态报告是什么时候回来的、状态码是什么。如果短信中心侧显示已投递但SP侧没收到状态报告,那问题就在回传通道上。如果短信中心侧压根没有投递记录,那问题在提交环节。

# 在短信中心日志里按message_id搜索投递记录 grep "MSG_ID=123456789" /var/log/smsc/smsc_delivery.log | grep -E "submit|deliver|report" # 输出示例: # 2024-01-15 10:23:45 submit_sm from 8613800138000 to 8613900139000 MSG_ID=123456789 # 2024-01-15 10:23:47 deliver_sm to MSC_001 MSG_ID=123456789 status=DELIVRD # 2024-01-15 10:23:48 report back to SP MSG_ID=123456789 status=DELIVRD

上面这段日志说明短信在2秒内完成了投递并回传了状态报告。如果只有第一行没有后面两行,说明短信卡在了短信中心内部,需要检查路由表或者接收方号码的归属MSC是否可达。

2.3 用SMPP协议对接时必调的4个参数

SMPP是SP平台和短信中心之间最常用的协议。对接时如果参数没调对,会出现连接频繁断开、短信提交超时、状态报告收不到等问题。下面这4个参数是我踩过坑之后每次都会重点检查的。

参数典型值作用调错后果
enquire_link_interval30秒心跳间隔设太长会被短信中心断开,设太短浪费信令
submit_sm_timeout5000毫秒提交超时设太短会误判失败,设太长会阻塞后续提交
window_size10滑动窗口大小设太大导致短信中心限流,设太小吞吐上不去
max_connection2最大连接数单连接故障时没有冗余,多连接要注意负载均衡
# SMPP客户端连接参数配置示例 import smpplib client = smpplib.client("smsc.example.com", 2775) client.set_message_sent_handler(lambda pdu: print(f"Sent: {pdu.sequence}")) client.set_message_received_handler(lambda pdu: print(f"Delivered: {pdu.sequence}")) # 关键参数设置 client.connect() client.bind_transceiver( system_id="sp_account", password="sp_password", enquire_link_interval=30, # 心跳间隔30秒 submit_sm_timeout=5000, # 提交超时5秒 )

这段代码里enquire_link_interval设成30秒是经验值,大部分短信中心在60秒无心跳后会断开连接。submit_sm_timeout设5秒是因为短信中心内部处理通常在1秒内完成,超过5秒基本可以判定为异常。window_size在bind_transceiver之后通过client.set_window_size(10)设置,控制同时未确认的PDU数量。

3. 计费与话单生成环节的实操要点

3.1 短信计费触发点在哪,为什么会有话单丢失

短信计费通常不是在短信中心完成的,而是在短信中心向计费系统发送话单之后。话单里包含发送方号码、接收方号码、短信类型、提交时间、状态等字段。计费系统根据话单生成扣费记录。问题在于:短信中心和高并发场景下,话单可能因为队列积压而丢失,或者因为格式错误被计费系统丢弃。

常见做法是:短信中心侧先把话单写入本地磁盘队列,再由一个独立的话单采集进程读取并发送给计费系统。这样即使计费系统暂时不可用,话单也不会丢。我一般会检查三个地方:短信中心的话单队列文件是否有积压、话单采集进程是否在运行、计费系统的话单入库日志是否有格式错误。

# 检查话单队列积压情况 ls -lh /var/spool/smsc/cdr/ | tail -5 # 如果文件数量持续增长且时间戳很旧,说明采集进程卡住了 # 检查话单采集进程 ps aux | grep cdr_collector # 如果没有输出,说明进程挂了,需要重启 # 检查计费系统入库日志 tail -100 /var/log/billing/cdr_import.log | grep -i "error\|reject"

3.2 话单字段映射的3个易错点

话单从短信中心到计费系统,中间可能经过格式转换。我遇到过最多的问题就是字段映射错误。比如短信中心用caller表示发送方,计费系统用calling_number,如果映射脚本写错了,计费系统就会把接收方当成发送方来扣费。还有时间格式,短信中心可能用Unix时间戳,计费系统要求YYYY-MM-DD HH:MM:SS,转换时区没处理对就会导致话单时间偏移8小时。

第三个易错点是短信类型编码。普通短信、长短信、国际短信、SP短信的计费费率不同,如果类型字段映射错了,要么少扣费要么多扣费。我一般会在话单采集进程里加一个校验逻辑:对每条话单检查发送方号码和接收方号码是否都是有效的手机号格式,时间戳是否在合理范围内,短信类型是否在已知枚举值里。校验不通过的话单写入单独的错误队列,人工介入处理。

4. 短信业务流程分析中的避坑与排查记录

4.1 状态报告延迟导致重复发送

现象:用户反馈收到两条一样的短信,但SP平台日志显示只提交了一次。

原因:短信中心投递成功后回传状态报告,但状态报告在信令通道上延迟了。SP平台在submit_sm_timeout时间内没收到状态报告,判定为失败并触发重试,于是短信中心又投递了一次。

解决:在SP平台侧维护一个已发送消息的message_id缓存,收到状态报告后更新状态。重试前先查缓存,如果该message_id已经有状态报告了就不再重试。同时把submit_sm_timeout适当调大,给状态报告留出回传时间。

4.2 长短信拆分后计费翻倍

现象:用户发送一条超过70个字符的短信,话单显示扣了两次费。

原因:长短信在短信中心会被拆分成多条普通短信分别投递,每条都会生成独立话单。如果计费系统没有做长短信合并,就会按条数扣费。

解决:在话单里增加concat_reference和concat_total字段,计费系统根据这两个字段判断是否属于同一条长短信,合并后只计一次费。短信中心侧也要确保拆分后的每条短信都带上相同的concat_reference。

4.3 SMPP连接被短信中心限流断开

现象:SP平台和短信中心之间的SMPP连接每隔几分钟就断开一次,日志显示ESME_RTHROTTLED。

原因:window_size设得太大,短时间内提交了大量短信,短信中心触发限流保护,主动断开连接。

解决:把window_size从默认的10降到5,同时在提交短信的代码里加一个令牌桶限流器,控制每秒提交的短信条数不超过短信中心允许的上限。具体上限需要跟短信中心侧确认,一般是每秒100到500条不等。

4.4 国际短信路由错误导致投递失败

现象:发送到境外号码的短信全部失败,状态报告显示UNDELIV。

原因:短信中心的路由表里没有配置该国家代码的出口路由,或者配置的出口网关不支持该运营商的协议。

解决:检查短信中心路由表,确认目标国家代码有对应的路由条目。如果没有,需要联系短信中心管理员添加。同时确认出口网关的协议类型(SMPP、CIMD、UCP等)和目标运营商的协议是否匹配。

4.5 话单时间戳时区不一致

现象:计费系统里的话单时间比实际发送时间早了8小时,导致夜间话单被算到了前一天。

原因:短信中心用UTC时间生成话单,计费系统按本地时间解析,中间没有做时区转换。

解决:在话单采集进程里统一把时间戳转成带时区的ISO 8601格式,比如2024-01-15T10:23:45+08:00。计费系统侧解析时按带时区的方式处理,避免歧义。

5. 用状态机模型验证短信业务流程的完整性

5.1 把短信生命周期画成状态机

短信从提交到最终状态,可以抽象成几个状态:SUBMITTED(已提交到短信中心)、ENROUTE(正在投递)、DELIVRD(已投递)、EXPIRED(过期)、DELETED(已删除)、UNDELIV(无法投递)、REJECTD(被拒绝)。每个状态之间的转换都有对应的触发条件。用状态机模型来验证业务流程,好处是能穷举所有可能的路径,发现遗漏的异常分支。

我一般会用一个简单的状态转换表来检查:从SUBMITTED出发,可能到ENROUTE、REJECTD、EXPIRED;从ENROUTE出发,可能到DELIVRD、UNDELIV、EXPIRED。如果实际日志里出现了状态转换表里没有的路径,比如从SUBMITTED直接到DELIVRD,那说明中间某个环节的日志丢了,或者状态报告被错误映射了。

5.2 用脚本自动校验状态转换合法性

# 短信状态转换合法性校验脚本 VALID_TRANSITIONS = { "SUBMITTED": ["ENROUTE", "REJECTD", "EXPIRED"], "ENROUTE": ["DELIVRD", "UNDELIV", "EXPIRED"], "DELIVRD": [], "UNDELIV": [], "EXPIRED": [], "REJECTD": [], "DELETED": [], } def validate_transition(prev_status, curr_status): """检查状态转换是否合法""" if prev_status not in VALID_TRANSITIONS: return False, f"未知的前置状态: {prev_status}" if curr_status not in VALID_TRANSITIONS[prev_status]: return False, f"非法转换: {prev_status} -> {curr_status}" return True, "合法" # 从日志里提取状态序列并逐条校验 status_sequence = ["SUBMITTED", "ENROUTE", "DELIVRD"] for i in range(1, len(status_sequence)): ok, msg = validate_transition(status_sequence[i-1], status_sequence[i]) if not ok: print(f"第{i}步异常: {msg}")

这个脚本的核心逻辑是维护一张合法转换表,然后对日志里的状态序列逐条检查。如果发现非法转换,比如SUBMITTED直接跳到DELIVRD,就需要去查中间是不是漏了ENROUTE的日志,或者状态报告里的状态码映射错了。实际使用的时候,我会把这个校验逻辑嵌入到日志分析管道里,每天定时跑一次,把异常转换的记录输出到报表里。

5.3 状态机验证发现的典型问题

用状态机跑一遍之后,最常见的发现是EXPIRED状态被滥用。有些短信中心在投递失败后不生成UNDELIV,而是直接标记为EXPIRED,导致SP平台无法区分是“对方关机导致过期”还是“路由错误导致无法投递”。这两种情况的处理策略完全不同:前者可以等对方开机后重试,后者需要修路由。所以我在对接短信中心时,会明确要求对方在状态报告里区分这两种状态码。

另一个常见问题是DELETED状态。这个状态通常出现在短信被短信中心主动删除的场景,比如存储满了需要清理。但如果DELETED出现在ENROUTE之后,说明短信在投递过程中被删了,这通常意味着短信中心内部有异常。我一般会把这个状态单独告警,因为正常业务流程里不应该出现。

5.4 一个实用技巧:用message_id串联全链路日志

最后分享一个我一直在用的技巧:在短信提交时生成一个全局唯一的message_id,然后把这个ID透传到短信中心、网关、计费系统的所有日志里。这样排查问题时,只需要拿一个message_id去各个系统的日志里搜,就能把整条链路的处理过程串起来。实现方式是在SMPP的submit_sm包里加一个可选参数message_payload或者用short_message字段的前几个字节存message_id。短信中心侧需要支持把这个ID透传到状态报告和话单里。

这个做法一开始需要跟短信中心侧协调,但一旦落地,排查效率会提升很多。以前查一条短信为什么失败,要在四五个系统之间来回翻日志,现在一个grep就能定位到卡在哪一环。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 11:40:28

计算机网络试讲:20分钟聚焦局域网与CSMA/CD教学设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 11:40:22

钢铁工控网络安全三层隔离实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 11:38:50

FPGA I/O约束与电平标准实战避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 11:37:17

FPGA实现LVDS接口Camera Sensor图像采集完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 11:36:52

IEEE 802.1Qca详解:TSN路径控制与资源预留核心标准

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 11:35:48

FPGA多主从AXI Interconnect配置实战:Crossbar架构与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华