SMPP协议解析
本文详细介绍了SMPP协议,一种在移动通信网络中发送和接收短信的二进制协议,涉及其基本特性、消息类型、流程、安全性和应用领域,重点讲解了Bind、Submit、Deliver等关键操作。
SMPP(Short Message Peer-to-Peer)是一种用于发送和接收短信消息的协议,通常用于移动通信网络。它允许应用程序通过短信中心(Short Message Service Center,SMSC)发送和接收短信。下面是对SMPP协议的详细解释。
SMPP(Short Message Peer-to-Peer Protocol)是用于短信服务的高效协议,解析其协议数据单元(PDU)需掌握以下核心要点:
1. SMPP协议基础
• 用途:连接短信网关(ESME)与短信中心(SMSC),支持短信提交、投递及状态查询。
• 版本:常见版本3.3、3.4、5.0,不同版本字段可能存在差异。
• 通信模式:基于TCP/IP,使用大端字节序(网络字节序)。
2. PDU结构解析
每个PDU包含Header(固定16字节)和Body(可变长度):
Header结构
struct {uint32 command_length; // 整个PDU长度(含Header和Body)uint32 command_id; // 命令类型(如submit_sm=0x00000004)uint32 command_status; // 响应状态(0表示成功)uint32 sequence_number; // 序列号(用于请求-响应匹配)
}
3. Body字段解析示例:submit_sm
# 字段顺序及类型(以SMPP 3.4为例)
service_type : COctetString(1-6) # 服务类型(如空字符串)
source_addr_ton : uint8 # 源地址类型(如国际号码=0x01)
source_addr_npi : uint8 # 源编号计划(如ISDN=0x01)
source_addr : COctetString(1-21) # 源地址(如手机号)
dest_addr_ton : uint8 # 目标地址类型
dest_addr_npi : uint8 # 目标编号计划
destination_addr : COctetString(1-21) # 目标地址
esm_class : uint8 # 消息类型(如普通消息=0x00)
protocol_id : uint8 # 协议标识(通常0x00)
priority_flag : uint8 # 优先级(0-3)
schedule_delivery_time: COctetString(0-17) # 定时发送时间(可选)
validity_period : COctetString(0-17) # 有效期(可选)
registered_delivery : uint8 # 回执标志(如需要状态报告=0x01)
replace_if_present : uint8 # 替换已存在消息(通常0x00)
data_coding : uint8 # 编码格式(如GSM-7=0x00,UCS-2=0x08)
sm_default_msg_id : uint8 # 默认消息ID(通常0x00)
sm_length : uint8 # 短信内容长度
short_message : OctetString(0-255) # 短信内容(或UDH+内容)
4. 解析流程
- 读取Header:提取长度、命令类型及序列号。
- 验证长度:确保接收的数据完整。
- 解析Body:按命令类型对应的字段顺序逐个解析。
- 处理可选参数:遍历剩余字节解析TLV。
- 处理编码:根据
<font style="color:rgb(51, 51, 51);">data_coding</font>
转换内容(如GSM7、UCS-2)。 - 组装长短信:通过UDH中的
<font style="color:rgb(51, 51, 51);">concat</font>
参数合并分段。
5. 代码示例(Python)
import structdef parse_header(data):return struct.unpack('>4I', data[:16])def parse_submit_sm(body):fields = {}# 解析固定字段ptr = 0fields['service_type'] = parse_c_octet(body[ptr:ptr+6]) # COctetString(6)ptr += 6fields['source_addr_ton'] = body[ptr]ptr +=1# ... 按顺序解析其他字段# 解析short_messagesm_length = body[ptr]ptr +=1fields['short_message'] = body[ptr:ptr+sm_length]ptr += sm_length# 解析可选参数while ptr < len(body):tag, length = struct.unpack('>HH', body[ptr:ptr+4])ptr +=4value = body[ptr:ptr+length]ptr +=lengthfields[f'tag_{tag:04X}'] = valuereturn fieldsdef parse_c_octet(data):end = data.find(b'\x00')return data[:end].decode('latin1') if end != -1 else data.decode('latin1')
6. 注意事项
• 字节序处理:所有多字节整数需使用大端序解析。
• 版本兼容:不同版本的字段差异需适配(如5.0新增字段)。
• 错误处理:校验<font style="color:rgb(51, 51, 51);">command_status</font>
,非零需处理错误码。
• 异步通信:通过<font style="color:rgb(51, 51, 51);">sequence_number</font>
匹配请求与响应。
以下是针对SMPP协议的流程图、应用场景及Java代码示例:
一、流程图示例
-
SMPP连接建立流程
-
短信提交(submit_sm)流程
- 长短信分片处理流程
二、应用场景
- 企业短信通知系统
• 场景:企业向用户发送验证码或订单通知。
• SMPP角色:
• ESME(企业短信平台)通过submit_sm
提交短信。
• SMSC(运营商网关)返回message_id
,并在投递后发送状态报告(deliver_sm
)。
- 用户上行短信(MO)处理
• 场景:用户回复短信(如退订指令)。
• SMPP角色:
• SMSC主动推送deliver_sm
到ESME,其中short_message
字段包含用户回复内容。
• ESME解析后触发业务逻辑(如更新用户状态)。
- 国际短信路由
• 场景:跨国企业向多国家用户发送短信。
• SMPP角色:
• 根据dest_addr_ton
(目标地址类型)和data_coding
(编码格式)自动适配运营商路由。
• 支持GSM-7、UCS-2等编码,确保内容兼容性。
三、Java代码示例
- 解析SMPP Header
public class SmppHeader {private int commandLength;private int commandId;private int commandStatus;private int sequenceNumber;public static SmppHeader parse(byte[] data) {ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);return new SmppHeader(buffer.getInt(),buffer.getInt(),buffer.getInt(),buffer.getInt());}
}
- 构造submit_sm PDU
public class SubmitSmBuilder {public static byte[] buildSubmitSm(String sourceAddr, String destAddr, String message) {ByteBuffer buffer = ByteBuffer.allocate(1024).order(ByteOrder.BIG_ENDIAN);// 固定字段buffer.put((byte) 0x00); // service_type (空)buffer.put((byte) 0x01); // source_addr_ton (国际号码)buffer.put((byte) 0x01); // source_addr_npi (ISDN)putCOctetString(buffer, sourceAddr, 21); // source_addrbuffer.put((byte) 0x01); // dest_addr_tonbuffer.put((byte) 0x01); // dest_addr_npiputCOctetString(buffer, destAddr, 21); // destination_addrbuffer.put((byte) 0x00); // esm_classbuffer.put((byte) 0x00); // protocol_idbuffer.put((byte) 0x00); // priority_flag// 短信内容(GSM-7编码)byte[] gsm7Bytes = encodeGsm7(message);buffer.put((byte) gsm7Bytes.length); // sm_lengthbuffer.put(gsm7Bytes); // short_message// 回退到实际长度并返回byte[] result = new byte[buffer.position()];System.arraycopy(buffer.array(), 0, result, 0, result.length);return result;}private static void putCOctetString(ByteBuffer buffer, String value, int maxLength) {byte[] bytes = value.getBytes(StandardCharsets.US_ASCII);int length = Math.min(bytes.length, maxLength - 1);buffer.put(bytes, 0, length);buffer.put((byte) 0x00); // 补\0结束符}
}
- 处理可选参数(TLV)
public class TlvParser {public static Map<Integer, byte[]> parseTlvs(ByteBuffer buffer) {Map<Integer, byte[]> tlvs = new HashMap<>();while (buffer.remaining() >= 4) {int tag = buffer.getShort() & 0xFFFF;int length = buffer.getShort() & 0xFFFF;byte[] value = new byte[length];buffer.get(value);tlvs.put(tag, value);}return tlvs;}
}
四、关键注意事项
- 编码处理
• 使用StandardCharsets.US_ASCII
处理GSM-7,StandardCharsets.UTF_16BE
处理UCS-2。
• 长短信需添加UDH头,并通过sar_msg_ref_num
等TLV参数关联分片。
- 异步通信
• 通过sequenceNumber
匹配请求和响应:
CompletableFuture<String> future = new CompletableFuture<>();
pendingRequests.put(sequenceNumber, future);
// 收到响应后通过sequenceNumber触发future.complete()
- 错误处理
• 检查commandStatus
:
if (header.getCommandStatus() != 0) {throw new SmppException("Error code: " + header.getCommandStatus());
}
以上内容覆盖了SMPP的核心流程、场景及Java实现关键代码,可直接用于开发短信网关或集成服务。