用Python实现Cmpp协议的教程

news2024/11/26 9:52:07

引言&协议概述

(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.coloradmin.cn/o/1941389.html

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

相关文章

重磅:2024上海不锈钢工业展览会-相聚12月

2024上海不锈钢工业展览会-相聚12月 时间&#xff1a;2024年12月18-20日 地点&#xff1a;上海新国际博览中心 2024上海国际不锈钢工业展览会规划30000平方米展览规模&#xff0c;预设展位1200个&#xff0c;将为国内外不锈钢产业提供一个集“展示、合作、交易、发展”于一…

使用 Web APi - MediaRecorder 获取麦克风资源,报错:Cannot find name ‘MediaRecorder‘ 的解决方法

目录 一、背景&#xff1a; 二、具体解决方法 一、背景&#xff1a; angular 调用 MediaRecorder 来使用麦克风获取声音&#xff0c;&#xff08;具体要求&#xff1a;angular 前端 按键调用 麦克风&#xff0c;松开按键生成音频文件&#xff09;代码如下&#xff08;来自通…

上市企业金融错配、信贷错配、资本错配程度,原始数据测算代码和结果整(1998-2023年)

1、数据来源&#xff1a;根据沪深A股上市公司数据进行测算 2、时间跨度&#xff1a;1998-2023年 3、区域范围&#xff1a;沪深A股上市公司 4、指标说明&#xff1a; 参考邵挺(2010)、周煜皓和张胜勇(2014)的研究&#xff0c;运用金融错配负担水平来衡量信贷错配&#xff08…

数据结构【没头单链表】

目录 ​ 概念与结构 结点 链表的性质 链表的打印分析 实现单链表&#xff1a; 创建单链表数据 申请空间 尾插数据 打印 头插数据 尾删 头删 查询数据 指定位置前插入数据 指定位置后插入数据 删除pos节点 删除pos后面的节点 销毁 链表的分类 链表说明&#…

unittest框架和pytest框架区别及示例

unittest框架和pytest框架区别及示例 类型unittest框架pytest框架unittest框架示例pytest框架示例安装python内置的一个单元测试框架,标准库&#xff0c;不需要安装第三方单元测试库&#xff0c;需要安装使用时直接引用 import unittest安装命令&#xff1a;pip3 install pyte…

博客建站4 - ssh远程连接服务器

1. 什么是SSH?2. 下载shh客户端3. 配置ssh密钥4. 连接服务器5. 常见问题 5.1. IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! 1. 什么是SSH? SSH&#xff08;Secure Shell&#xff09;是一种加密的网络协议&#xff0c;用于在不安全的网络中安全地远程登录到其他…

刚刚 威尼斯影评人周公布 2024 年电影阵容 包括敏感纪录片《本土》

《本土》 威尼斯影评人周是威尼斯电影节专门为首次拍摄电影的人设立的侧边活动&#xff0c;该活动公布了第 39 届威尼斯电影节的七部竞赛片和两部非竞赛片的入选名单&#xff0c;第 39 届威尼斯电影节将于 8 月 28 日至 9 月 7 日举行。 较为及时的作品之一是美国导演迈克尔普…

工业互联网带来什么变革?详解工业互联网产业模式与业务模式!

随着互联网技术的不断进步&#xff0c;工业互联网产业模式应运而生&#xff0c;成为制造业服务化延伸的新引擎。这种模式突破了传统制造业的局限&#xff0c;将服务与产品全生命周期紧密结合&#xff0c;实现了从单一产品制造向提供综合服务的转变。本文将分析工业互联网如何利…

1.30、基于卷积神经网络的手写数字旋转角度预测(matlab)

1、卷积神经网络的手写数字旋转角度预测原理及流程 基于卷积神经网络的手写数字旋转角度预测是一个常见的计算机视觉问题。在这种情况下&#xff0c;我们可以通过构建一个卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;来实现该任务。以下…

操作线程的方法

文章目录 前言一、线程的生命周期二、线程的操作方法 1.休眠2.加入3.中断4.礼让总结 前言 将线程看作一个生命的开始和结束&#xff0c;更好理解它各个状态的变化。同时该文会介绍操作线程的主要方法来控制线程的生命周期。这些方法的使用和线程生命周期的变化是密切相关的。 一…

甄选范文“论面向方面的编程技术及其应”,软考高级论文,系统架构设计师论文

论文真题 针对应用开发所面临的规模不断扩大、复杂度不断提升的问题,面向方面的编程(Aspect Oriented Programming,AOP)技术提供了一种有效的程序开发方法。为了理解和完成一个复杂的程序,通常要把程序进行功能划分和封装。一般系统中的某些通用功能,如安全性、持续性、日…

Intellij IDEA 的Plugins加载不出来的解决方法

一、点开插件---右上角设置---HTTP代理设置 二、勾选自动检测代理设置 输入url&#xff1a; https://plugins.jetbrains.com/ 配置完成后&#xff0c;点击确定。 然后点击检查连接&#xff0c;再一次输入那个URL&#xff0c;一般来说可以连接成功了 然后 重启IDEA以刷新缓…

详解数据结构之二叉树(堆)

详解数据结构之二叉树(堆) 树 树的概念 树是一个非线性结构的数据结构&#xff0c;它是由 n(n>0)个有限节点组成的一个具有层次关系的集合&#xff0c;它的外观形似一颗倒挂着的树&#xff0c;根朝上&#xff0c;叶朝下&#xff0c;所以称呼为树。每颗子树的根节点有且只…

7. 聚类算法 KMeans

聚类算法 KMeans 1. 应用&#xff1a;大数据杀熟2. 迭代法3. 代码 1. 应用&#xff1a;大数据杀熟 618、双十一&#xff0c;平台要对用户进行分类&#xff1a;用户&#xff1a; 脑残粉&#xff08;不降价&#xff0c;或者涨点价&#xff09;墙头草&#xff08;给点小优惠券&am…

二叉树基础及实现(一)

目录&#xff1a; 一. 树的基本概念 二. 二叉树概念及特性 三. 二叉树的基本操作 一. 树的基本概念&#xff1a; 1 概念 &#xff1a; 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0 &#xff09;个有限结点组成一个具有层次关系的集合。 把它叫做树是因…

数据结构之初始二叉树(4)

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;数据结构&#xff08;Java版&#xff09; 二叉树的基本操作 二叉树的相关刷题&#xff08;上&#xff09;通过上篇文章的学习&#xff0c;我们…

基于密钥的身份验证(Linux-Linux)

A主机&#xff1a; 1、生成密钥对 [rootservera ~]# ssh-keygen查看公钥 注&#xff1a;id_rsa为私钥&#xff08;证书&#xff09;&#xff0c;id_rsa.pub为公钥 2、注册公钥到服务器 [rootservera ~]# ssh-copy-id root172.25.250.106 查看.ssh 3、使用密钥连接服务器 #…

ViT(Vision Transformer)网络结构详解

本文在transformer的基础上对ViT进行讲解&#xff0c;transformer相关部分可以看我另一篇博客&#xff08;transformer中对于QKV的个人理解-CSDN博客&#xff09;。 一、网络结构概览 上图展示了Vision Transformer (ViT) 的基本架构&#xff0c;我按照运行顺序分为三个板块进…

配置web服务器

当访问网站www.haha.com时显示&#xff1a;haha&#xff1b;当访问网站www.xixi.com/secret/显示&#xff1a;this is secret 第一步&#xff0c;配置一个新的IP 确认后 esc返回 第二步&#xff1a;重启ens160 第三步&#xff1a;创建目录&#xff0c;并且在文件内写入内容 第…