用Python实现Cmpp协议的教程

引言&协议概述

(CMPP)是中国移动为实现短信业务而制定的一种通信协议,用于在客户端(SP,Service Provider)和中国移动短信网关之间传输短消息,有时也叫做移动梦网短信业务。CMPP3.0是该协议的第三个版本,相比于前两个版本,它增加了对长短信的支持、优化了数据结构等。本文对CMPP协议进行介绍,并给出Python实现CMPP协议栈的思路。

Python的asyncio模块提供了一套简洁的异步IO编程模型,非常适合用于实现协议栈。

CMPP协议基于客户端/服务端模型工作。由客户端(短信应用,如手机,应用程序等)先和ISMG(Internet Short Message Gateway 互联网短信网关)建立起TCP长连接,并使用CMPP命令与ISMG进行交互,实现短信的发送和接收。在CMPP协议中,无需同步等待响应就可以发送下一个指令,实现者可以根据自己的需要,实现同步、异步两种消息传输模式,满足不同场景下的性能要求。

连接成功,发送短信并查询短信发送成功

连接成功,从ISMG接收到短信

协议帧介绍

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

image.png

CMPP Header

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

  • Total Length:整个PDU的长度,包括Header和Body。
  • Command ID:用于标识PDU的类型(例如,Connect、Submit等)。
  • Sequence Id:序列号,用来匹配请求和响应。

用Python Asyncio实现CMPP协议栈里的建立连接

可以以本文的代码作为基础,很容易地在上面扩展。

代码结构组织如下:

.
├── LICENSE
├── README.md
├── cmpp
│   ├── __init__.py
│   ├── client.py
│   ├── protocol.py
│   └── utils.py
├── requirements.txt
├── setup.cfg
└── setup.py
  • cmpp/protocol.py:定义不同 CMPP 协议数据单元 (PDU) 的数据类,包括 CmppHeader、CmppConnect、CmppConnectResp、CmppSubmit 和 CmppSubmitResp
  • cmpp/client.py:该类处理与 ISMG(互联网短消息网关)的连接以及发送/接收 PDU。 主要 asyncio 进行异步 I/O 操作
  • cmpp/utils.py:定义 BoundAtomic 类,它是一种线程安全的方式来管理具有最小值和最大值的序列号。保证CMPP序列号在一定的范围内
  • setup.py:配置要分发的包,指定包名称、版本、作者和依赖项等元数据。

利用Python锁实现sequence_id

sequence_id是从1到0x7FFFFFFF的值

import threadingclass BoundAtomic:def __init__(self, min_val: int, max_val: int):assert min_val <= max_val, "min must be less than or equal to max"self.min = min_valself.max = max_valself.value = min_valself.lock = threading.Lock()def next_val(self) -> int:with self.lock:if self.value >= self.max:self.value = self.minelse:self.value += 1return self.value

在Python中定义CMPP PDU,篇幅有限,仅定义数个PDU

from dataclasses import dataclass
from typing import Union, List@dataclass
class CmppHeader:total_length: intcommand_id: intsequence_id: int@dataclass
class CmppConnect:source_addr: strauthenticator_source: bytesversion: inttimestamp: int@dataclass
class CmppConnectResp:status: intauthenticator_ismg: strversion: int@dataclass
class CmppSubmit:msg_id: intpk_total: intpk_number: intregistered_delivery: intmsg_level: intservice_id: strfee_user_type: intfee_terminal_id: strfee_terminal_type: inttp_pid: inttp_udhi: intmsg_fmt: intmsg_src: strfee_type: strfee_code: strvalid_time: strat_time: strsrc_id: strdest_usr_tl: intdest_terminal_id: List[str]dest_terminal_type: intmsg_length: intmsg_content: byteslink_id: str@dataclass
class CmppSubmitResp:msg_id: intresult: int@dataclass
class CmppPdu:header: CmppHeaderbody: Union[CmppHeader, CmppConnectResp, CmppSubmit, CmppSubmitResp]

实现编解码方法

@dataclass
class CmppConnect:source_addr: strauthenticator_source: bytesversion: int# MMDDHHMMSS formattimestamp: intdef encode(self) -> bytes:source_addr_bytes = self.source_addr.encode('utf-8').ljust(6, b'\x00')version_byte = self.version.to_bytes(1, 'big')timestamp_bytes = self.timestamp.to_bytes(4, 'big')return source_addr_bytes + self.authenticator_source + version_byte + timestamp_bytes@dataclass
class CmppConnectResp:status: intauthenticator_ismg: strversion: int@staticmethoddef decode(data: bytes) -> 'CmppConnectResp':status = int.from_bytes(data[0:4], 'big')authenticator_ismg = data[4:20].rstrip(b'\x00').decode('utf-8')version = data[20]return CmppConnectResp(status=status, authenticator_ismg=authenticator_ismg, version=version)@dataclass
class CmppPdu:header: CmppHeaderbody: Union[CmppConnect, CmppConnectResp, CmppSubmit, CmppSubmitResp]def encode(self) -> bytes:body_bytes = self.body.encode()self.header.total_length = len(body_bytes) + 12header_bytes = (self.header.total_length.to_bytes(4, 'big') +self.header.command_id.to_bytes(4, 'big') +self.header.sequence_id.to_bytes(4, 'big'))return header_bytes + body_bytes@staticmethoddef decode(data: bytes) -> 'CmppPdu':header = CmppHeader(total_length=int.from_bytes(data[0:4], 'big'),command_id=int.from_bytes(data[4:8], 'big'),sequence_id=int.from_bytes(data[8:12], 'big'))body_data = data[12:header.total_length]if header.command_id == CONNECT_RESP_ID:body = CmppConnectResp.decode(body_data)else:raise NotImplementedError("not implemented yet.")return CmppPdu(header=header, body=body)

asyncio tcp流相关代码

class CmppClient:def __init__(self, host: str, port: int):self.host = hostself.port = portself.sequence_id = BoundAtomic(1, 0x7FFFFFFF)self.reader = Noneself.writer = Noneasync def connect(self):self.reader, self.writer = await asyncio.open_connection(self.host, self.port)async def close(self):if self.writer:self.writer.close()

实现同步的connect_ismg方法

    async def connect_ismg(self, request: CmppConnect):if self.writer is None or self.reader is None:raise ConnectionError("Client is not connected")sequence_id = self.sequence_id.next_val()header = CmppHeader(0, command_id=CONNECT_ID, sequence_id=sequence_id)pdu: CmppPdu = CmppPdu(header=header, body=request)self.writer.write(pdu.encode())await self.writer.drain()length_bytes = await self.reader.readexactly(4)response_length = int.from_bytes(length_bytes)response_data = await self.reader.readexactly(response_length)return CmppPdu.decode(response_data)

运行example,验证连接成功

async def main():client = CmppClient(host='localhost', port=7890)await client.connect()print("Connected to ISMG")connect_request = CmppConnect(source_addr='source_addr',authenticator_source=b'authenticator_source',version=0,timestamp=1122334455,)connect_response = await client.connect_ismg(connect_request)print(f"Connect response: {connect_response}")await client.close()print("Connection closed")asyncio.run(main())

image.png

总结

本文简单对CMPP协议进行了介绍,并尝试用python实现协议栈,但实际商用发送短信往往更加复杂,面临诸如流控、运营商对接、传输层安全等问题,可以选择华为云消息&短信(Message & SMS)服务

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/50604.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

通过iframe碎片实现web局部打印

通过iframe碎片实现web局部打印 创建打印模板 首先&#xff0c;创建一个出货单的 HTML 模板&#xff0c;并用 CSS 进行样式设计。 tips: 1、直接通过iframe碎片拉起打印&#xff0c;会导致样式丢失&#xff0c;所以需要获取当前界面的样式。 ${Array.from(document.querySel…

嵌入式Linux学习: 设备树实验

设备树&#xff08;DeviceTree&#xff09;是一种硬件描述机制&#xff0c;用于在嵌入式系统和操作系统中描述硬件设备的特性、连接关系和配置信息。它提供了一种与平台无关的方式来描述硬件&#xff0c;使得内核与硬件之间的耦合度降低&#xff0c;提高了系统的可移植性和可维…

立创梁山派--移植开源的SFUD和FATFS实现SPI-FLASH文件系统

本文主要是在sfud的基础上进行fatfs文件系统的移植&#xff0c;并不对sfud的移植再进行过多的讲解了哦&#xff0c;所以如果想了解sfud的移植过程&#xff0c;请参考我的另外一篇文章&#xff1a;传送门 正文开始咯 首先我们需要先准备资料准备好&#xff0c;这里对于fatfs的…

第五节shell脚本中的运行流程控制(5.3)

六, 流程中断控制器 在程序运行时因为需求我们需要在某个位置中断 常用的流程控制器有一下几个 控制器名称功能return退出函数contune终止档次循环, 提前进入下一个循环break终止所在循环exit退出脚本 示例: func() { for i in {1..10} do[ "$i" -eq "4&qu…

【ESP32 IDF 软件模拟SPI驱动 W25Q64存储与读取数组】

目录 SPISPI介绍SPI时序代码编写&#xff08;spi&w25q64&#xff09; 代码调试 SPI SPI介绍 SPI&#xff08;Serial Peripheral Interface&#xff0c;串行外围设备接口&#xff09;是一种高速、全双工、同步的串行通信总线&#xff0c;常用于微控制器与各种外围设备&…

苍穹外卖浏览器前端界面修改

背景&#xff1a; 客户原始方案是期望做一个Spring Boot Vue的饿了么系统&#xff0c;但时间上太仓促&#xff0c;所以建议选择开源的苍穹外码目作为作业提交。 客户接受了建议的方案后&#xff0c;期望对前端页面做一些个性化的定制修改。 过程&#xff1a; 苍穹外卖简单介…

【HTML+CSS】HTML超链接:构建网页导航的基石

目录 什么是HTML超链接&#xff1f; 基本语法 示例 链接到另一个网页 链接到同一页面内的不同部分 常用属性 在Web开发的广阔世界中&#xff0c;HTML&#xff08;HyperText Markup Language&#xff09;作为网页内容的标准标记语言&#xff0c;扮演着至关重要的角色。而在…

重拾CSS,前端样式精读-函数(颜色,计算,图像和图形)

前言 本文收录于CSS系列文章中&#xff0c;欢迎阅读指正 在计算机编程中&#xff0c;函数有着重要的作用和意义&#xff0c;它可以实现封装&#xff0c;复用&#xff0c;模块化&#xff0c;参数等功能效果&#xff0c;在如何在CSS中写变量&#xff1f;一文带你了解前端样式利…

操作系统杂项(十)

目录 一、简述socket中select、epoll的使用场景和区别 1、使用场景 2、区别 二、epoll水平触发和边缘触发的区别 三、简述Reactor和Proactor模式 1、Reactor 2、Proactor 3、区别 四、简述同步和异步的区别&#xff0c;阻塞和非阻塞的区别 1、同步与异步 2、阻塞与非…

Greenplum数据库中常用函数

聚合函数&#xff1a; SUM&#xff1a;计算某一列的总和。例如&#xff0c;SELECT SUM(sales) FROM transactions; 可以计算出transactions表中sales列的总和。AVG&#xff1a;计算某一列的平均值。例如&#xff0c;SELECT AVG(price) FROM products; 可以计算出products表中pr…

数据分析之一:方差分析

系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 TODO:写完再整理 文章目录 系列文章目录前言一、数据方差分析原理二、数据方差分析在机器人领域的应用1、单因素方差分析(One-Way ANOVA)2、双因素方差分析(Two-Way ANOVA)3、多因素方差分…

在VS IDE中​​​​​​​搜索所有带有中文的字符串

搜索所有带有中文的字符串 要搜索所有包含中文的字符串&#xff0c;可以使用正则表达式功能来查找包含 中文字符的所有字符串。步骤如下&#xff1a; 1. 打开搜索功能 &#xff1a; o 按 Ctrl Shift F 打开“查找在文件”对话框。 2. 输入正则表达式 &#xff1a; …

RTTI的开启和关闭

RTTI是运行时类型识别&#xff0c;C引入这个机制是为了让程序在运行时能根据基类的指针或引用来获得该指针或引用所指的对象的实际类型。&#xff08;多态&#xff09; 我们使用dynamic_cast就是基于RTTI的&#xff0c;我们可以通过编译器对RTTI进行开启或者关闭&#xff0c;关…

基于ssm+vue医院住院管理系统源码数据库

摘 要 随着时代的发展&#xff0c;医疗设备愈来愈完善&#xff0c;医院也变成人们生活中必不可少的场所。如今&#xff0c;已经2021年了&#xff0c;虽然医院的数量和设备愈加完善&#xff0c;但是老龄人口也越来越多。在如此大的人口压力下&#xff0c;医院住院就变成了一个…

Go 语言任务编排 WaitGroup

WaitGroup 是常用的 Go 同步原语之一,用来做任务编排。它要解决的就是并发-等待的问题: 现在有一个 goroutine A 在检查点 ( checkpoint ) 等待一组 goroutine 全部完成它们的任务,如果这些 goroutine 还没全部完成任务,那么 goroutine A 就会被阻塞在检查点,直到所有的 …

深入分析 Android ContentProvider (五)

文章目录 深入分析 Android ContentProvider (五)ContentProvider 的性能优化和实践案例1. 性能优化技巧1.1. 数据库索引优化示例&#xff1a;添加索引 1.2. 批量操作与事务管理示例&#xff1a;批量插入操作 1.3. 使用异步操作示例&#xff1a;使用 AsyncTask 进行异步查询 1.…

Linux:基础

一、安装 二、 一些组件 2.1 git管理 集中式版本控制系统:版本库是集中存放在中央服务器的,需要时要先从中央服务器取得最新的版本进行修改,修改后再推送给中央服务器。集中式版本控制系统最大的毛病就是必须联网才能工作,网速慢的话影响太大。 分布式版本控制系统:分布…

Linux网络-wget命令

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注我&#xff0c;我尽量把自己会的都分享给大家&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 Linux服务器作为一个常用的网络服务器&#xff0c;主要的作用就是向客户端提供网络…

设计模式14-享元模式

设计模式14-享元模式 由来动机定义与结构代码推导特点享元模式的应用总结优点缺点使用享元模式的注意事项 由来动机 在很多应用中&#xff0c;可能会创建大量相似对象&#xff0c;例如在文字处理器中每个字符对象。在这些场景下&#xff0c;如果每个对象都独立存在&#xff0c…

PyCharm 2024.1.4:一站式教程与新特性解析

简介 PyCharm是由JetBrains开发的一款Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;自发布以来&#xff0c;凭借其强大的功能、智能的代码补全、广泛的插件支持和用户友好的界面&#xff0c;成为了Python开发者的首选工具之一。无论是数据科学、Web开发还是其他…