用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 threading

class 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_val
        self.max = max_val
        self.value = min_val
        self.lock = threading.Lock()

    def next_val(self) -> int:
        with self.lock:
            if self.value >= self.max:
                self.value = self.min
            else:
                self.value += 1
            return self.value

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

from dataclasses import dataclass
from typing import Union, List

@dataclass
class CmppHeader:
    total_length: int
    command_id: int
    sequence_id: int

@dataclass
class CmppConnect:
    source_addr: str
    authenticator_source: bytes
    version: int
    timestamp: int

@dataclass
class CmppConnectResp:
    status: int
    authenticator_ismg: str
    version: int

@dataclass
class CmppSubmit:
    msg_id: int
    pk_total: int
    pk_number: int
    registered_delivery: int
    msg_level: int
    service_id: str
    fee_user_type: int
    fee_terminal_id: str
    fee_terminal_type: int
    tp_pid: int
    tp_udhi: int
    msg_fmt: int
    msg_src: str
    fee_type: str
    fee_code: str
    valid_time: str
    at_time: str
    src_id: str
    dest_usr_tl: int
    dest_terminal_id: List[str]
    dest_terminal_type: int
    msg_length: int
    msg_content: bytes
    link_id: str

@dataclass
class CmppSubmitResp:
    msg_id: int
    result: int

@dataclass
class CmppPdu:
    header: CmppHeader
    body: Union[CmppHeader, CmppConnectResp, CmppSubmit, CmppSubmitResp]

实现编解码方法

@dataclass
class CmppConnect:
    source_addr: str
    authenticator_source: bytes
    version: int
    # MMDDHHMMSS format
    timestamp: int

    def 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: int
    authenticator_ismg: str
    version: int

    @staticmethod
    def 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: CmppHeader
    body: Union[CmppConnect, CmppConnectResp, CmppSubmit, CmppSubmitResp]

    def encode(self) -> bytes:
        body_bytes = self.body.encode()
        self.header.total_length = len(body_bytes) + 12
        header_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

    @staticmethod
    def 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 = host
        self.port = port
        self.sequence_id = BoundAtomic(1, 0x7FFFFFFF)
        self.reader = None
        self.writer = None

    async 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.mfbz.cn/a/843629.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在线 PDF 制作者泄露用户上传的文档

两家在线 PDF 制作者泄露了数万份用户文档&#xff0c;包括护照、驾驶执照、证书以及用户上传的其他个人信息。 我们都经历过这样的情况&#xff1a;非常匆忙&#xff0c;努力快速制作 PDF 并提交表单。许多人向在线 PDF 制作者寻求帮助&#xff0c;许多人的祈祷得到了回应。 …

【单目3D检测】smoke(1):模型方案详解

纵目发表的这篇单目3D目标检测论文不同于以往用2D预选框建立3D信息&#xff0c;而是采取直接回归3D信息&#xff0c;这种思路简单又高效&#xff0c;并不需要复杂的前后处理&#xff0c;而且是一种one stage方法&#xff0c;对于实际业务部署也很友好。 题目&#xff1a;SMOKE&…

StringBuilder, Stringbuffer,StringJoiner

StringBuilder StringBuilder 代表可变字符串对象&#xff0c;相当于是一个容器&#xff0c;里面装的字符串是可以改变的&#xff0c;就是用来操作字符串的。 StringBuilder 比String更适合做字符串的修改操作&#xff0c;效率更高&#xff0c;代码更加的简洁。 public clas…

Hadoop3:MR程序处理小文件的优化办法(uber模式)

一、解决方案 1、在数据采集的时候&#xff0c;就将小文件或小批数据合成大文件再上传HDFS&#xff08;数据源头&#xff09; 2、Hadoop Archive&#xff08;存储方向&#xff09; 是一个高效的将小文件放入HDFS块中的文件存档工具&#xff0c;能够将多个小文件打包成一个HAR…

Java--反射

反射是什么 反射允许对成员变量&#xff0c;成员方法和构造方法的信息进行编程访问 获取class对象的三种方式 代码 package a2;public class Student {private String name;private int age;public Student(){}public Student(String name,int age){this.name name;this.age …

处理uniapp刷新后,点击返回按钮跳转到登录页的问题

在使用uniapp的原生返回的按钮时&#xff0c;如果没有刷新会正常返回到对应的页面&#xff0c;如果刷新后会在当前页反复横跳&#xff0c;或者跳转到登录页。那个时候我第一个想法时&#xff1a;使用浏览器的history.back()方法。因为浏览器刷新后还是可以通过右上角的返回按钮…

package.json中对peerDependencies的理解

peerDependencies只要是用来限制依赖的&#xff0c;最近在开发的时候有遇到这样的问题&#xff0c;所以研究了一下 "peerDependencies": {"vue/composition-api": "^1.0.5","vue/runtime-core": "^3.0.0","echarts&q…

数据库-练习

题目要求&#xff1a;按照要求建立数据库与表&#xff0c;并完成相应的查询操作 解题步骤如下代码所示&#xff1a; //建立相关的数据库mydb8_worker mysql> show databases; -------------------- | Database | -------------------- | information_schema | | …

MySQL通过bin-log恢复数据

MySQL通过bin-log恢复数据 1.bin-log说明2.数据恢复流程2.1 查看是否开启bin-log2.3 查看bin-log2.4 执行数据恢复操作2.5 检查数据是否恢复 1.bin-log说明 mysqldump和bin-log都可以作为MySQL数据库备份的方式&#xff1a; mysqldump 用于将整个或部分数据库导出为可执行的S…

spring-boot 整合 redisson 实现延时队列(文末有彩蛋)

应用场景 通常在一些需要经历一段时间或者到达某个指定时间节点才会执行的功能&#xff0c;比如以下这些场景&#xff1a; 订单超时提醒收货自动确认会议提醒代办事项提醒 为什么使用延时队列 对于数据量小且实时性要求不高的需求来说&#xff0c;最简单的方法就是定时扫描数据…

Odoo17架构概述

多层架构 Odoo遵循多层架构&#xff0c;这意味着演示&#xff0c;业务逻辑和数据存储是分开的。更具体地说&#xff0c;它使用三层架构。 UI展示层 UI表示层是 HTML5、JavaScript 和 CSS 的组合。 应用程序的最顶层是用户界面。界面的主要功能是将任务和结果转换为用户可以理…

MacBook电脑远程连接Linux系统的服务器方法

一、问题简介 Windows 操作系统的电脑可使用Xshell等功能强大的远程连接软件。通过连接软件&#xff0c;用户可以在一台电脑上访问并控制另一台远程计算机。这对于远程技术支持、远程办公等场景非常有用。但是MacBook电脑的macOS无法使用Xshell。 在Mac上远程连接到Windows服…

解决npm install(‘proxy‘ config is set properly. See: ‘npm help config‘)失败问题

摘要 重装电脑系统后&#xff0c;使用npm install初始化项目依赖失败了&#xff0c;错误提示&#xff1a;‘proxy’ config is set properly…&#xff0c;具体的错误提示如下图所示&#xff1a; 解决方案 经过报错信息查询解决办法&#xff0c;最终找到了两个比较好的方案&a…

最新可用度盘不限速后台系统源码_去授权开心版

某宝同款度盘不限速后台系统源码&#xff0c;验证已被我去除&#xff0c;两个后端系统&#xff0c;账号和卡密系统 第一步安装宝塔&#xff0c;部署卡密系统&#xff0c;需要环境php7.4 把源码丢进去&#xff0c;设置php7.4&#xff0c;和伪静态为thinkphp直接访问安装就行 …

MLIR的TOY教程学习笔记

MLIR TOY Language 文章目录 MLIR TOY Language如何编译该项目ch1: MLIR 前端IR解析ch2: 定义方言和算子 (ODS)1. 定义方言2. 定义OP3. OP相关操作4. 定义OP ODS (Operation Definition Specification)1. 基本定义2. 添加文档3. 验证OP4. 新增构造函数5. 定义打印OP的格式 ch3:…

简单工厂、工厂方法与抽象工厂之间的区别

简单工厂、工厂方法与抽象工厂之间的区别 1、简单工厂&#xff08;Simple Factory&#xff09;1.1 定义1.2 特点1.3 示例场景 2、工厂方法&#xff08;Factory Method&#xff09;2.1 定义2.2 特点2.3 示例场景 3、抽象工厂&#xff08;Abstract Factory&#xff09;3.1 定义3.…

视频共享融合赋能平台LntonCVS视频监控管理平台视频云解决方案

LntonCVS是基于国家标准GB28181协议开发的视频监控与云服务平台&#xff0c;支持多设备同时接入。该平台能够处理和分发多种视频流格式&#xff0c;包括RTSP、RTMP、FLV、HLS和WebRTC。主要功能包括视频直播监控、云端录像与存储、检索回放、智能告警、语音对讲和平台级联&…

buuctf web 第五到八题

[ACTF2020 新生赛]Exec 这里属实有点没想到了&#xff0c;以为要弹shell&#xff0c;结果不用 127.0.0.1;ls /PING 127.0.0.1 (127.0.0.1): 56 data bytes bin dev etc flag home lib media mnt opt proc root run sbin srv sys tmp usr var127.0.0.1;tac /f*[GXYCTF2019]Pin…

全球大模型将往何处去?

在这个信息爆炸的时代&#xff0c;我们如同站在知识的海洋边&#xff0c;渴望着能够驾驭帆船&#xff0c;探索那些深邃的奥秘。 而今天&#xff0c;我们将启航&#xff0c;透过一份精心编制的报告&#xff0c;去洞察全球大模型的未来趋势&#xff0c;探索人工智能的无限可能。…

C++初学者指南-5.标准库(第一部分)--标准库查询存在算法

C初学者指南-5.标准库(第一部分)–标准库查询存在算法 文章目录 C初学者指南-5.标准库(第一部分)--标准库查询存在算法any_of / all_of / none_ofcountcount_if相关内容 不熟悉 C 的标准库算法&#xff1f; ⇒ 简介 any_of / all_of / none_of 如果在输入范围(所有元素…