4

教你用Perl实现Smgp协议 - 华为云开发者联盟

 1 week ago
source link: https://www.cnblogs.com/huaweiyun/p/18174373
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

使用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;

image.png

相关开源项目

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK