本文分享自華為雲社群《華為雲簡訊服務教你用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或使用群發助手,即可使用驗證碼、通知簡訊服務。
點選關注,第一時間瞭解華為雲新鮮技術~