教你用Perl实现Smgp协议

打印 上一主题 下一主题

主题 628|帖子 628|积分 1884

本文分享自华为云社区《华为云短信服务教你用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协议栈里的创建连接
  1. ├── Makefile.PL
  2. ├── examples
  3. │   └── smgp_client_login_example.pl
  4. └── lib
  5.     └── Smgp
  6.         ├── BoundAtomic.pm
  7.         ├── Client.pm
  8.         ├── Constant.pm
  9.         └── Protocol.pm
复制代码
Makefile.PL:用来生成Makefileexamples:存放示例代码

  • 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的值
  1. package Smgp::BoundAtomic;
  2. use strict;
  3. use warnings FATAL => 'all';
  4. sub new {
  5.     my ($class, %args) = @_;
  6.     my $self = {
  7.         min     => $args{min},
  8.         max     => $args{max},
  9.         value   => $args{min},
  10.     };
  11.     bless $self, $class;
  12.     return $self;
  13. }
  14. sub increment {
  15.     my ($self) = @_;
  16.     if ($self->{value} >= $self->{max}) {
  17.         $self->{value} = $self->{min};
  18.     } else {
  19.         $self->{value}++;
  20.     }
  21.     return $self->{value};
  22. }
  23. sub get {
  24.     my ($self) = @_;
  25.     return $self->{value};
  26. }
  27. 1;
复制代码
在Perl中定义SMGP PDU以及编解码函数
  1. package Smgp::Protocol;
  2. use strict;
  3. use warnings FATAL => 'all';
  4. use Smgp::Constant;
  5. sub new_login {
  6.     my ($class, %args) = @_;
  7.     my $self = {
  8.         clientId            => $args{clientId},
  9.         authenticatorClient => $args{authenticatorClient},
  10.         loginMode           => $args{loginMode},
  11.         timeStamp           => $args{timeStamp},
  12.         version             => $args{version},
  13.     };
  14.     return bless $self, $class;
  15. }
  16. sub encode_login {
  17.     my ($self) = @_;
  18.     return pack("A8A16CNC", @{$self}{qw(clientId authenticatorClient loginMode timeStamp version)});
  19. }
  20. sub decode_login_resp {
  21.     my ($class, $buffer) = @_;
  22.     my ($status, $authenticatorServer, $version) = unpack("N4A16C", $buffer);
  23.     return bless {
  24.         status              => $status,
  25.         authenticatorServer => $authenticatorServer,
  26.         version             => $version,
  27.     }, $class;
  28. }
  29. sub new_header {
  30.     my ($class, %args) = @_;
  31.     my $self = {
  32.         total_length   => $args{total_length},
  33.         request_id     => $args{request_id},
  34.         command_status => $args{command_status},
  35.         sequence_id    => $args{sequence_id},
  36.     };
  37.     return bless $self, $class;
  38. }
  39. sub encode_header {
  40.     my ($self, $total_length) = @_;
  41.     return pack("N3", $total_length, @{$self}{qw(request_id sequence_id)});
  42. }
  43. sub new_pdu {
  44.     my ($class, %args) = @_;
  45.     my $self = {
  46.         header => $args{header},
  47.         body   => $args{body},
  48.     };
  49.     return bless $self, $class;
  50. }
  51. sub encode_login_pdu {
  52.     my ($self) = @_;
  53.     my $encoded_body = $self->{body}->encode_login();
  54.     return $self->{header}->encode_header(length($encoded_body) + 12) . $encoded_body;
  55. }
  56. sub decode_pdu {
  57.     my ($class, $buffer) = @_;
  58.     my ($request_id, $sequence_id) = unpack("N2", substr($buffer, 0, 8));
  59.     my $body_buffer = substr($buffer, 8);
  60.     my $header = $class->new_header(
  61.         total_length   => 0,
  62.         request_id     => $request_id,
  63.         sequence_id    => $sequence_id,
  64.     );
  65.     my $body;
  66.     if ($request_id == Smgp::Constant::LOGIN_RESP_ID) {
  67.         $body = $class->decode_login_resp($body_buffer);
  68.     } else {
  69.         die "Unsupported request_id: $request_id";
  70.     }
  71.     return $class->new_pdu(
  72.         header => $header,
  73.         body   => $body,
  74.     );
  75. }
  76. 1;
复制代码
constant.pm存放相关requestId
  1. package Smgp::Constant;
  2. use strict;
  3. use warnings FATAL => 'all';
  4. use constant {
  5.     LOGIN_ID               => 0x00000001,
  6.     LOGIN_RESP_ID          => 0x80000001,
  7.     SUBMIT_ID              => 0x00000002,
  8.     SUBMIT_RESP_ID         => 0x80000002,
  9.     DELIVER_ID             => 0x00000003,
  10.     DELIVER_RESP_ID        => 0x80000003,
  11.     ACTIVE_TEST_ID         => 0x00000004,
  12.     ACTIVE_TEST_RESP_ID    => 0x80000004,
  13.     FORWARD_ID             => 0x00000005,
  14.     FORWARD_RESP_ID        => 0x80000005,
  15.     EXIT_ID                => 0x00000006,
  16.     EXIT_RESP_ID           => 0x80000006,
  17.     QUERY_ID               => 0x00000007,
  18.     QUERY_RESP_ID          => 0x80000007,
  19.     MT_ROUTE_UPDATE_ID     => 0x00000008,
  20.     MT_ROUTE_UPDATE_RESP_ID => 0x80000008,
  21. };
  22. 1;
复制代码
实现client以及login方法
  1. package Smgp::Client;
  2. use strict;
  3. use warnings FATAL => 'all';
  4. use IO::Socket::INET;
  5. use Smgp::Protocol;
  6. use Smgp::Constant;
  7. sub new {
  8.     my ($class, %args) = @_;
  9.     my $self = {
  10.         host => $args{host} // 'localhost',
  11.         port => $args{port} // 9000,
  12.         socket => undef,
  13.         sequence_id => 1,
  14.     };
  15.     bless $self, $class;
  16.     return $self;
  17. }
  18. sub connect {
  19.     my ($self) = @_;
  20.     $self->{socket} = IO::Socket::INET->new(
  21.         PeerHost => $self->{host},
  22.         PeerPort => $self->{port},
  23.         Proto => 'tcp',
  24.     ) or die "Cannot connect to $self->{host}:$self->{port} $!";
  25. }
  26. sub login {
  27.     my ($self, $body) = @_;
  28.     my $header = Smgp::Protocol->new_header(
  29.         request_id     => Smgp::Constant::LOGIN_ID,
  30.         sequence_id    => 1,
  31.     );
  32.     my $pdu = Smgp::Protocol->new_pdu(
  33.         header => $header,
  34.         body   => $body,
  35.     );
  36.     $self->{socket}->send($pdu->encode_login_pdu());
  37.     $self->{socket}->recv(my $response_length_bytes, 4);
  38.     my $total_length = unpack("N", $response_length_bytes);
  39.     my $remain_length = $total_length - 4;
  40.     $self->{socket}->recv(my $response_data, $remain_length);
  41.     return Smgp::Protocol->decode_pdu($response_data)->{body};
  42. }
  43. sub disconnect {
  44.     my ($self) = @_;
  45.     close($self->{socket}) if $self->{socket};
  46. }
  47. 1;
复制代码
运行example,验证连接乐成
  1. package smgp_client_login_example;
  2. use strict;
  3. use warnings FATAL => 'all';
  4. use Smgp::Client;
  5. use Smgp::Protocol;
  6. use Smgp::Constant;
  7. sub main {
  8.     my $client = Smgp::Client->new(
  9.         host => 'localhost',
  10.         port => 9000,
  11.     );
  12.     $client->connect();
  13.     my $login = Smgp::Protocol->new_login(
  14.         clientId            => '12345678',
  15.         authenticatorClient => '1234567890123456',
  16.         loginMode           => 1,
  17.         timeStamp           => time(),
  18.         version             => 0,
  19.     );
  20.     my $response = $client->login($login);
  21.     if ($response->{status} == 0) {
  22.         print "Login successful!\n";
  23.     }
  24.     else {
  25.         print "Login failed! Status: ", (defined $response->{status} ? $response->{status} : 'undefined'), "\n";
  26.     }
  27.     $client->disconnect();
  28. }
  29. main() unless caller;
  30. 1;
复制代码

相关开源项目

总结

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

祗疼妳一个

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表