写点什么

教你用 Perl 实现 Smgp 协议

  • 2024-05-06
    广东
  • 本文字数:4069 字

    阅读完需:约 13 分钟

教你用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
复制代码


Makefile.PL:用来生成 Makefile


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;
复制代码


相关开源项目


总结


本文简单对 SMGP 协议进行了介绍,并尝试用 perl 实现协议栈,但实际商用发送短信往往更加复杂,面临诸如流控、运营商对接、传输层安全等问题,可以选择华为云消息 &短信(Message & SMS)服务通过 HTTP 协议接入华为云短信服务是华为云携手全球多家优质运营商和渠道,为企业用户提供的通信服务。企业调用 API 或使用群发助手,即可使用验证码、通知短信服务。


点击关注,第一时间了解华为云新鲜技术~

发布于: 刚刚阅读数: 4
用户头像

提供全面深入的云计算技术干货 2020-07-14 加入

生于云,长于云,让开发者成为决定性力量

评论

发布
暂无评论
教你用Perl实现Smgp协议_Linux_华为云开发者联盟_InfoQ写作社区