教你用Rust实现Smpp协议

打印 上一主题 下一主题

主题 915|帖子 915|积分 2745

本文分享自华为云社区《华为云短信服务教你用Rust实现Smpp协议》,作者: 张俭。
协议概述

SMPP(Short Message Peer-to-Peer)协议起源于90年代,最初由Aldiscon公司开发,后来由SMPP开发者论坛维护和推广。SMPP常用于在SMSC(Short Message Service Center,短信中心)和短信应用之间传输短消息,支持高效的短信息发送、接收和查询功能,是电信运营商和短信服务提供商之间互通短信的主要协议之一。
SMPP协议基于客户端/服务端模型工作。由客户端(短信应用,如手机,应用程序等)先和SMSC建立起TCP长连接,并使用SMPP命令与SMSC进行交互,实现短信的发送和接收。在SMPP协议中,无需同步等待响应就可以发送下一个指令,实现者可以根据自己的需要,实现同步、异步两种消息传输模式,满足不同场景下的性能要求。
时序图

绑定transmitter模式,发送短信并查询短信发送成功


绑定receiver模式,从SMSC接收到短信


协议帧介绍


在SMPP协议中,每个PDU都包含两个部分:SMPP Header和SMPP Body。
SMPP Header

Header包含以下字段,大小长度都是4字节:

  • Command Length:整个PDU的长度,包括Header和Body。
  • Command ID:用于标识PDU的类型(例如,BindReceiver、QuerySM等)。
  • Command Status:响应状态码,表示处理的结果。
  • Sequence Number:序列号,用来匹配请求和响应。
用Rust实现SMPP协议栈里的BindTransmitter

本文的代码均已上传到smpp-rust
选用Tokio作为基础的异步运行时环境,tokio有非常强大的异步IO支持,也是rust库的事实标准。
代码结构组织如下:
  1. ├── lib.rs
  2. ├── const.rs
  3. ├── protocol.rs
  4. ├── smpp_client.rs
  5. └── smpp_server.rs
复制代码

  • lib.rs Rust项目的入口点
  • const.rs 包含常量定义,如commandId、状态码等
  • protocol.rs 包含PDU定义,编解码处理等
  • smpp_client.rs 实现smpp客户端逻辑
  • smpp_server.rs 实现
利用rust原子类实现sequence_number

sequence_number是从1到0x7FFFFFFF的值,利用Rust的AtomicI32来生成这个值。
  1. use std::sync::atomic::{AtomicI32, Ordering};
  2. use std::num::TryFromIntError;
  3. struct BoundAtomicInt {
  4.     min: i32,
  5.     max: i32,
  6.     integer: AtomicI32,
  7. }
  8. impl BoundAtomicInt {
  9.     pub fn new(min: i32, max: i32) -> Self {
  10.         assert!(min <= max, "min must be less than or equal to max");
  11.         Self {
  12.             min,
  13.             max,
  14.             integer: AtomicI32::new(min),
  15.         }
  16.     }
  17.     pub fn next_val(&self) -> Result<i32, TryFromIntError> {
  18.         let next = self.integer.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| {
  19.             Some(if x >= self.max { self.min } else { x + 1 })
  20.         })?;
  21.         Ok(next)
  22.     }
  23. }
复制代码
在Rust中定义SMPP PDU
  1. pub struct SmppPdu {
  2.     pub header: SmppHeader,
  3.     pub body: SmppBody,
  4. }
  5. pub struct SmppHeader {
  6.     pub command_length: i32,
  7.     pub command_id: i32,
  8.     pub command_status: i32,
  9.     pub sequence_number: i32,
  10. }
  11. pub enum SmppBody {
  12.     BindReceiver(BindReceiver),
  13.     BindReceiverResp(BindReceiverResp),
  14.     BindTransmitter(BindTransmitter),
  15.     BindTransmitterResp(BindTransmitterResp),
  16.     QuerySm(QuerySm),
  17.     QuerySmResp(QuerySmResp),
  18.     SubmitSm(SubmitSm),
  19.     SubmitSmResp(SubmitSmResp),
  20.     DeliverSm(DeliverSm),
  21.     DeliverSmResp(DeliverSmResp),
  22.     Unbind(Unbind),
  23.     UnbindResp(UnbindResp),
  24.     ReplaceSm(ReplaceSm),
  25.     ReplaceSmResp(ReplaceSmResp),
  26.     CancelSm(CancelSm),
  27.     CancelSmResp(CancelSmResp),
  28.     BindTransceiver(BindTransceiver),
  29.     BindTransceiverResp(BindTransceiverResp),
  30.     Outbind(Outbind),
  31.     EnquireLink(EnquireLink),
  32.     EnquireLinkResp(EnquireLinkResp),
  33.     SubmitMulti(SubmitMulti),
  34.     SubmitMultiResp(SubmitMultiResp),
  35. }
复制代码
实现编解码方法
  1. impl SmppPdu {
  2.     pub fn encode(&self) -> Vec<u8> {
  3.         let mut body_buf = match &self.body {
  4.             SmppBody::BindTransmitter(bind_transmitter) => bind_transmitter.encode(),
  5.             _ => unimplemented!(),
  6.         };
  7.         let command_length = (body_buf.len() + 16) as i32;
  8.         let header = SmppHeader {
  9.             command_length,
  10.             command_id: self.header.command_id,
  11.             command_status: self.header.command_status,
  12.             sequence_number: self.header.sequence_number,
  13.         };
  14.         let mut buf = header.encode();
  15.         buf.append(&mut body_buf);
  16.         buf
  17.     }
  18.     pub fn decode(buf: &[u8]) -> io::Result<Self> {
  19.         let header = SmppHeader::decode(&buf[0..16])?;
  20.         let body = match header.command_id {
  21.             constant::BIND_TRANSMITTER_RESP_ID => SmppBody::BindTransmitterResp(BindTransmitterResp::decode(&buf[16..])?),
  22.             _ => unimplemented!(),
  23.         };
  24.         Ok(SmppPdu { header, body })
  25.     }
  26. }
  27. impl SmppHeader {
  28.     pub(crate) fn encode(&self) -> Vec<u8> {
  29.         let mut buf = vec![];
  30.         buf.extend_from_slice(&self.command_length.to_be_bytes());
  31.         buf.extend_from_slice(&self.command_id.to_be_bytes());
  32.         buf.extend_from_slice(&self.command_status.to_be_bytes());
  33.         buf.extend_from_slice(&self.sequence_number.to_be_bytes());
  34.         buf
  35.     }
  36.     pub(crate) fn decode(buf: &[u8]) -> io::Result<Self> {
  37.         if buf.len() < 16 {
  38.             return Err(io::Error::new(io::ErrorKind::InvalidData, "Buffer too short for SmppHeader"));
  39.         }
  40.         let command_id = u32::from_be_bytes(buf[0..4].try_into().unwrap());
  41.         let command_status = i32::from_be_bytes(buf[4..8].try_into().unwrap());
  42.         let sequence_number = i32::from_be_bytes(buf[8..12].try_into().unwrap());
  43.         Ok(SmppHeader {
  44.             command_length: 0,
  45.             command_id,
  46.             command_status,
  47.             sequence_number,
  48.         })
  49.     }
  50. }
  51. impl BindTransmitter {
  52.     pub(crate) fn encode(&self) -> Vec<u8> {
  53.         let mut buf = vec![];
  54.         write_cstring(&mut buf, &self.system_id);
  55.         write_cstring(&mut buf, &self.password);
  56.         write_cstring(&mut buf, &self.system_type);
  57.         buf.push(self.interface_version);
  58.         buf.push(self.addr_ton);
  59.         buf.push(self.addr_npi);
  60.         write_cstring(&mut buf, &self.address_range);
  61.         buf
  62.     }
  63.     pub(crate) fn decode(buf: &[u8]) -> io::Result<Self> {
  64.         let mut offset = 0;
  65.         let system_id = read_cstring(buf, &mut offset)?;
  66.         let password = read_cstring(buf, &mut offset)?;
  67.         let system_type = read_cstring(buf, &mut offset)?;
  68.         let interface_version = buf[offset];
  69.         offset += 1;
  70.         let addr_ton = buf[offset];
  71.         offset += 1;
  72.         let addr_npi = buf[offset];
  73.         offset += 1;
  74.         let address_range = read_cstring(buf, &mut offset)?;
  75.         Ok(BindTransmitter {
  76.             system_id,
  77.             password,
  78.             system_type,
  79.             interface_version,
  80.             addr_ton,
  81.             addr_npi,
  82.             address_range,
  83.         })
  84.     }
  85. }
复制代码
实现同步的bind_transmitter方法
  1. pub async fn bind_transmitter(
  2.         &mut self,
  3.         bind_transmitter: BindTransmitter,
  4.     ) -> io::Result<BindTransmitterResp> {
  5.         if let Some(stream) = &mut self.stream {
  6.             let sequence_number = self.sequence_number.next_val();
  7.             let pdu = SmppPdu {
  8.                 header: SmppHeader {
  9.                     command_length: 0,
  10.                     command_id: constant::BIND_TRANSMITTER_ID,
  11.                     command_status: 0,
  12.                     sequence_number,
  13.                 },
  14.                 body: SmppBody::BindTransmitter(bind_transmitter),
  15.             };
  16.             let encoded_request = pdu.encode();
  17.             stream.write_all(&encoded_request).await?;
  18.             let mut length_buf = [0u8; 4];
  19.             stream.read_exact(&mut length_buf).await?;
  20.             let msg_length = u32::from_be_bytes(length_buf) as usize - 4;
  21.             let mut msg_buf = vec![0u8; msg_length];
  22.             stream.read_exact(&mut msg_buf).await?;
  23.             let response = SmppPdu::decode(&msg_buf)?;
  24.             if response.header.command_status != 0 {
  25.                 Err(io::Error::new(
  26.                     io::ErrorKind::Other,
  27.                     format!("Error response: {:?}", response.header.command_status),
  28.                 ))
  29.             } else {
  30.                 // Assuming response.body is of type BindTransmitterResp
  31.                 match response.body {
  32.                     SmppBody::BindTransmitterResp(resp) => Ok(resp),
  33.                     _ => Err(io::Error::new(io::ErrorKind::InvalidData, "Unexpected response body")),
  34.                 }
  35.             }
  36.         } else {
  37.             Err(io::Error::new(io::ErrorKind::NotConnected, "Not connected"))
  38.         }
  39.     }
复制代码
运行example,验证连接成功
  1. use smpp_rust::protocol::BindTransmitter;
  2. use smpp_rust::smpp_client::SmppClient;
  3. #[tokio::main]
  4. async fn main() -> Result<(), Box<dyn std::error::Error>> {
  5.     let mut client = SmppClient::new("127.0.0.1", 2775);
  6.     client.connect().await?;
  7.     let bind_transmitter = BindTransmitter{
  8.         system_id: "system_id".to_string(),
  9.         password: "password".to_string(),
  10.         system_type: "system_type".to_string(),
  11.         interface_version: 0x34,
  12.         addr_ton: 0,
  13.         addr_npi: 0,
  14.         address_range: "".to_string(),
  15.     };
  16.     client.bind_transmitter(bind_transmitter).await?;
  17.     client.close().await?;
  18.     Ok(())
  19. }
复制代码

相关开源项目

总结

本文简单对SMPP协议进行了介绍,并尝试用rust实现协议栈,但实际商用发送短信往往更加复杂,面临诸如流控、运营商对接、传输层安全等问题,可以选择华为云消息&短信(Message & SMS)服务,华为云短信服务是华为云携手全球多家优质运营商和渠道,为企业用户提供的通信服务。企业调用API或使用群发助手,即可使用验证码、通知短信服务。
 
点击关注,第一时间了解华为云新鲜技术~
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

商道如狼道

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表