教你用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样例
- 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或使用群发助手,即可使用验证码、通知短信服务。
关注公众号
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
DDD与微服务架构浅析
DDD与微服务架构浅析 一、软件技术架构的演进 我们都知道这些年随着设备以及技术的发展,软件架构发生了很多变化,从最初的单机(BS/CS)架构到后面的集中式架构,再到如今的微服务架构, 现在基本可以说是微服务架构盛行的时代, DDD早在2004年就由埃里克·埃文斯提出, 但一直处于一个不愠不火的状态,直到Martin Fowler的《Microservices》引起大家注意, 也就是微服务盛行之后, DDD再次回到人们视野中间,为什么呢 ?我们先看一下三种技术架构的演进以及主要区别: 单机架构: 面向过程的设计方法、包括客户端UI和数据库两层,特征是整个开发围绕着数据库进行设计和开发。 集中式架构: 采用面向对象的设计方法,包括业务的接入层、业务逻辑层、数据访问层,这种架构很容易某一层或者几层变得臃肿,扩展性较差, 另外摩尔定律失效, 单台机器性能有限。 微服务架构: 微服务架构可以很好的实现应用直接的解耦,解决单体应用扩展性和弹性伸缩能力不足的问题。在集中式架构中, 系统分析、设计和开发往往是独立进行的,而且各个阶段负责人可能不一样,那么就涉及到交流信息丢失的问题, 另外项目从分析到...
-
下一篇
Python文本统计与分析从基础到进阶
本文分享自华为云社区《Python文本统计与分析从基础到进阶》,作者:柠檬味拥抱。 在当今数字化时代,文本数据无处不在,它们包含了丰富的信息,从社交媒体上的帖子到新闻文章再到学术论文。对于处理这些文本数据,进行统计分析是一种常见的需求,而Python作为一种功能强大且易于学习的编程语言,为我们提供了丰富的工具和库来实现文本数据的统计分析。本文将介绍如何使用Python来实现文本英文统计,包括单词频率统计、词汇量统计以及文本情感分析等。 单词频率统计 单词频率统计是文本分析中最基本的一项任务之一。Python中有许多方法可以实现单词频率统计,以下是其中一种基本的方法: def count_words(text): # 将文本中的标点符号去除并转换为小写 text = text.lower() for char in '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~': text = text.replace(char, ' ') # 将文本拆分为单词列表 words = text.split() # 创建一个空字典来存储单词...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- MySQL数据库中FOR UPDATE的使用
- Docker容器配置,解决镜像无法拉取问题
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS8编译安装MySQL8.0.19
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7,CentOS8安装Elasticsearch6.8.6



微信收款码
支付宝收款码