教你用Perl实现Smgp协议
本文分享自华为云社区《华为云短信服务教你用Perl实现Smgp协议》,作者:张俭。
引言&协议概述
中国电信短消息网关协议(SMGP)是中国网通为实现短信业务而制定的一种通信协议,全称叫做Short Message Gateway Protocol,用于在短消息网关(SMGW)和服务提供商(SP)之间、短消息网关(SMGW)和短消息网关(SMGW)之间通信。
Perl是一个老牌脚本语言,在众多Linux系统上都会默认安装,比如在ubuntu的22.04版本的基础镜像中,甚至没有Python,但是依然安装了Perl,Perl的普及度可见一斑。Perl的IO::Async模块提供了一套简洁的异步IO编程模型。
SMGP 协议基于客户端/服务端模型工作。由客户端(短信应用,如手机,应用程序等)先和短信网关(SMGW Short Message Gateway)建立起 TCP 长连接,并使用 CNGP 命令与SMGW进行交互,实现短信的发送和接收。在CNGP协议中,无需同步等待响应就可以发送下一个指令,实现者可以根据自己的需要,实现同步、异步两种消息传输模式,满足不同场景下的性能要求。
时序图
连接成功,发送短信
连接成功,从SMGW接收到短信
协议帧介绍
SMGP Header
Header包含以下字段,大小长度都是4字节
- Packet Length:整个PDU的长度,包括Header和Body。
- Request ID:用于标识PDU的类型(例如,Login、Submit等)。
- Sequence Id:序列号,用来匹配请求和响应。
使用perl实现SMGP协议栈里的建立连接
├── Makefile.PL
├── examples
│ └── smgp_client_login_example.pl
└── lib
└── Smgp
├── BoundAtomic.pm
├── Client.pm
├── Constant.pm
└── Protocol.pm
examples:存放示例代码
- smgp_client_login_example.pl:存放Smgp的login样例
lib/Smgp:包含所有的Perl模块文件
- BoundAtomic.pm:递增工具类,用来生成SequenceId
- Client.pm:Smgp定义,负责与Smgp服务进行通信,例如建立连接、发送短信等
- Protocol.pm:存放PDU,编解码等
实现sequence_id递增
sequence_id是从1到0x7FFFFFFF的值
package Smgp::BoundAtomic; use strict;
use warnings FATAL => 'all'; sub new {
my ($class, %args) = @_;
my $self = {
min => $args{min},
max => $args{max},
value => $args{min},
};
bless $self, $class;
return $self;
} sub increment {
my ($self) = @_;
if ($self->{value} >= $self->{max}) {
$self->{value} = $self->{min};
} else {
$self->{value}++;
}
return $self->{value};
} sub get {
my ($self) = @_;
return $self->{value};
} 1;
在Perl中定义SMGP PDU以及编解码函数
package Smgp::Protocol; use strict;
use warnings FATAL => 'all'; use Smgp::Constant; sub new_login {
my ($class, %args) = @_;
my $self = {
clientId => $args{clientId},
authenticatorClient => $args{authenticatorClient},
loginMode => $args{loginMode},
timeStamp => $args{timeStamp},
version => $args{version},
};
return bless $self, $class;
} sub encode_login {
my ($self) = @_;
return pack("A8A16CNC", @{$self}{qw(clientId authenticatorClient loginMode timeStamp version)});
} sub decode_login_resp {
my ($class, $buffer) = @_;
my ($status, $authenticatorServer, $version) = unpack("N4A16C", $buffer);
return bless {
status => $status,
authenticatorServer => $authenticatorServer,
version => $version,
}, $class;
} sub new_header {
my ($class, %args) = @_;
my $self = {
total_length => $args{total_length},
request_id => $args{request_id},
command_status => $args{command_status},
sequence_id => $args{sequence_id},
};
return bless $self, $class;
} sub encode_header {
my ($self, $total_length) = @_;
return pack("N3", $total_length, @{$self}{qw(request_id sequence_id)});
} sub new_pdu {
my ($class, %args) = @_;
my $self = {
header => $args{header},
body => $args{body},
};
return bless $self, $class;
} sub encode_login_pdu {
my ($self) = @_;
my $encoded_body = $self->{body}->encode_login();
return $self->{header}->encode_header(length($encoded_body) + 12) . $encoded_body;
} sub decode_pdu {
my ($class, $buffer) = @_;
my ($request_id, $sequence_id) = unpack("N2", substr($buffer, 0, 8));
my $body_buffer = substr($buffer, 8); my $header = $class->new_header(
total_length => 0,
request_id => $request_id,
sequence_id => $sequence_id,
); my $body;
if ($request_id == Smgp::Constant::LOGIN_RESP_ID) {
$body = $class->decode_login_resp($body_buffer);
} else {
die "Unsupported request_id: $request_id";
} return $class->new_pdu(
header => $header,
body => $body,
);
} 1;
constant.pm存放相关requestId
package Smgp::Constant; use strict;
use warnings FATAL => 'all'; use constant {
LOGIN_ID => 0x00000001,
LOGIN_RESP_ID => 0x80000001,
SUBMIT_ID => 0x00000002,
SUBMIT_RESP_ID => 0x80000002,
DELIVER_ID => 0x00000003,
DELIVER_RESP_ID => 0x80000003,
ACTIVE_TEST_ID => 0x00000004,
ACTIVE_TEST_RESP_ID => 0x80000004,
FORWARD_ID => 0x00000005,
FORWARD_RESP_ID => 0x80000005,
EXIT_ID => 0x00000006,
EXIT_RESP_ID => 0x80000006,
QUERY_ID => 0x00000007,
QUERY_RESP_ID => 0x80000007,
MT_ROUTE_UPDATE_ID => 0x00000008,
MT_ROUTE_UPDATE_RESP_ID => 0x80000008,
}; 1;
实现client以及login方法
package Smgp::Client;
use strict;
use warnings FATAL => 'all';
use IO::Socket::INET; use Smgp::Protocol;
use Smgp::Constant; sub new {
my ($class, %args) = @_;
my $self = {
host => $args{host} // 'localhost',
port => $args{port} // 9000,
socket => undef,
sequence_id => 1,
};
bless $self, $class;
return $self;
} sub connect {
my ($self) = @_;
$self->{socket} = IO::Socket::INET->new(
PeerHost => $self->{host},
PeerPort => $self->{port},
Proto => 'tcp',
) or die "Cannot connect to $self->{host}:$self->{port} $!";
} sub login {
my ($self, $body) = @_;
my $header = Smgp::Protocol->new_header(
request_id => Smgp::Constant::LOGIN_ID,
sequence_id => 1,
); my $pdu = Smgp::Protocol->new_pdu(
header => $header,
body => $body,
); $self->{socket}->send($pdu->encode_login_pdu()); $self->{socket}->recv(my $response_length_bytes, 4); my $total_length = unpack("N", $response_length_bytes);
my $remain_length = $total_length - 4;
$self->{socket}->recv(my $response_data, $remain_length); return Smgp::Protocol->decode_pdu($response_data)->{body};
} sub disconnect {
my ($self) = @_;
close($self->{socket}) if $self->{socket};
} 1;
运行example,验证连接成功
package smgp_client_login_example; use strict;
use warnings FATAL => 'all'; use Smgp::Client;
use Smgp::Protocol;
use Smgp::Constant; sub main {
my $client = Smgp::Client->new(
host => 'localhost',
port => 9000,
); $client->connect(); my $login = Smgp::Protocol->new_login(
clientId => '12345678',
authenticatorClient => '1234567890123456',
loginMode => 1,
timeStamp => time(),
version => 0,
); my $response = $client->login($login); if ($response->{status} == 0) {
print "Login successful!\n";
}
else {
print "Login failed! Status: ", (defined $response->{status} ? $response->{status} : 'undefined'), "\n";
} $client->disconnect();
} main() unless caller; 1;
相关开源项目
- netty-codec-sms 存放各种SMS协议(如cmpp、sgip、smpp)的netty编解码器
- sms-client-java 存放各种SMS协议的Java客户端
- sms-server-java 存放各种SMS协议的Java服务端
- cmpp-python cmpp协议的python实现
- cngp-zig cmpp协议的python实现
- smgp-perl smgp协议的perl实现
- smpp-rust smpp协议的rust实现
总结
本文简单对SMGP协议进行了介绍,并尝试用perl实现协议栈,但实际商用发送短信往往更加复杂,面临诸如流控、运营商对接、传输层安全等问题,可以选择华为云消息&短信(Message & SMS)服务通过HTTP协议接入,华为云短信服务是华为云携手全球多家优质运营商和渠道,为企业用户提供的通信服务。企业调用API或使用群发助手,即可使用验证码、通知短信服务。
教你用Perl实现Smgp协议的更多相关文章
- 教你用Perl 实现Base64编码
在用脚本后台发送邮件时,需要将html的内容转换成Base64编码的形式,这样邮件客户端会自动对Base64编码的内容进行解码,还原成原来的内容. Base64.pl: #!/usr/bin/perl ...
- 中国移动CMPP协议、联通SGIP协议、电信SMGP协议短信网关
移动cmpp协议 英文缩写:CMPP (China Mobile Peer to Peer) 中文名称:中国移动通信互联网短信网关接口协议 说明:为中国移动通信集团公司企业规范.规范中描述了中国移动短 ...
- C#实现电信短信SMGP协议程序源码
此程序为中国电信SMGP协议程序接口,适合在中国电信申请了短信发送端口的公司使用. 短信群发已经成为现在软件系统.网络营销等必不可少的应用工具.可应用在短信验证.信息群发.游戏虚拟商品购买.事件提醒. ...
- [Micropython][ESP8266] TPYBoard V202 之MQTT协议接入OneNET云平台
随着移动互联网的发展,MQTT由于开放源代码,耗电量小等特点,将会在移动消息推送领域会有更多的贡献,在物联网领域,传感器与服务器的通信,信息的收集,MQTT都可以作为考虑的方案之一.在未来MQTT会进 ...
- Lua编写wireshark插件初探——解析Websocket上的MQTT协议
一.背景 最近在做物联网流量分析时发现, App在使用MQTT协议时往往通过SSL+WebSocket+MQTT这种方式与服务器通信,在使用SSL中间人截获数据后,Wireshark不能自动解析出MQ ...
- usb库文件usb_desc.c分析
参考<圈圈教你玩USB> usb协议中使用的是小端结构,所以实际数据在传输时是低字节在先的. 设备描述符的实现: 已知每个设备都必须有且仅有一个设备描述符,它的结构在USB协议中有详细的定 ...
- DRF之REST规范介绍及View请求流程分析
编程是数据结构和算法的结合,而在Web类型的App中,我们对于数据的操作请求是通过url来承载的,本文详细介绍了REST规范和CBV请求流程. 编程是数据结构和算法的结合,小程序如简单的计算器,我们输 ...
- REST、DRF(View源码解读、APIView源码解读)
一 . REST 前言 1 . 编程 : 数据结构和算法的结合 .小程序如简单的计算器,我们输入初始数据,经过计算,得到最终的数据,这个过程中,初始数据和结果数据都是数据,而计算 ...
- CS基础课不完全自学指南
本文讲的是计算机学生怎么自学专业课,说长点就是该如何借助网络上已有的高质量学习资源(主要是公开课)来系统性的来点亮自己的CS技能树.这篇文章完全就是一篇自学性质的指南,需要对编程充满热情,起码觉得编程 ...
- Python基础教程之udp和tcp协议介绍
Python基础教程之udp和tcp协议介绍 UDP介绍 UDP --- 用户数据报协议,是一个无连接的简单的面向数据报的运输层协议.UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但 ...
随机推荐
- Java字符串比较 == 和 equals方法的区别
今天在排除一个bug的时候出现了一个很低级但是也很容易被忽视的错误,在此写了一个小例子做记录. 首先我先说一下错误的场景,我读取了一段json数据,并使用JSONObject的实例对象的getStri ...
- Python 列表list方法clear( )和直接list [ ]的区别
x.clear()是将内存地址清空, x=[ ]会新开辟一个内存空间.
- 11.硬核的volatile考点分析
大家好,我是王有志.关注王有志,一起聊技术,聊游戏,聊在外漂泊的生活. 今天我们学习并发编程中另一个重要的关键字volatile,虽然面试中它的占比低于synchronized,但依旧是不可忽略的内容 ...
- RedisTemplate 的简单使用
redisTemplate.opsForValue() 方法可以获得一个 Redis String 的操作类,通过该类可以执行一系列字符串类型数据的操作,例如获取.设置.删除数据等. // 示例 1: ...
- 提高生产力!这10个Lambda表达式必须掌握,开发效率嘎嘎上升!
在Java8及更高版本中,Lambda表达式的引入极大地提升了编程的简洁性和效率.本文将围绕十个关键场景,展示Lambda如何助力提升开发效率,让代码更加精炼且易于理解. 集合遍历 传统的for-ea ...
- 提升面试成功率:深入理解 C++ 11 新特性
C++11是C++语言的一个重大更新,引入了许多新特性,包括自动类型推导.lambda表达式.右值引用.智能指针等等.这些新特性使得C++更加现代化.高效.易用.也是面试容很容易被问到一个问题,下面我 ...
- HarmonyOS SDK开放能力,服务鸿蒙生态建设,打造优质应用体验
华为开发者大会2023(HDC.Together)于8月4日至6日在东莞松山湖举行,在HarmonyOS端云开放能力技术分论坛上,华为为广大开发者们介绍了HarmonyOS SDK开放能力在基础开发架 ...
- 【Kotlin】List、Set、Map简介
1 List Java 的 List.Set.Map 介绍见 → Java容器及其常用方法汇总. 1.1 创建 List 1.1.1 emptyList var list = emptyList& ...
- .NET周刊【4月第1期 2024-04-07】
国内文章 一个程序员的编年史 https://www.cnblogs.com/lunacy/p/18117213 作者拥有15年软件开发经验,曾在多家公司工作,项目和团队起伏充满变数.2007年,在太 ...
- HarmonyOS应用性能与功耗云测试
性能测试 性能测试主要验证HarmonyOS应用在华为真机设备上运行的性能问题,包括启动时长.界面显示.CPU占用和内存占用.具体性能测试项的详细说明请参考性能测试标准. 性能测试支持Phone和TV ...