首页 > 编程语言 >教你用Perl实现Smgp协议

教你用Perl实现Smgp协议

时间:2024-05-06 10:12:29浏览次数:26  
标签:Smgp self args Perl id ID my 教你用

本文分享自华为云社区《华为云短信服务教你用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接收到短信

     

协议帧介绍

image.png

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;

image.png

相关开源项目

总结

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

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

标签:Smgp,self,args,Perl,id,ID,my,教你用
From: https://www.cnblogs.com/huaweiyun/p/18174373

相关文章

  • 【Python&文字识别】基于HyperLPR3实现车牌检测和识别(Python版本快速部署)
        闲来无事,想复现一下网上的基于YOLOv5的单目测距算法。然后就突然想在这个场景下搞一下车牌识别,于是就有了这篇文章。今天就给大家分享基于HyperLPR3实现车牌检测和识别。原创作者:RS迷途小书童博客地址:https://blog.csdn.net/m0_56729804?type=blog1、HyperLP......
  • 教你用vbs实现微信自动发送消息功能
    <divid="navCategory"><h5class="catalogue">目录</h5><ulclass="first_class_ul"><li><ahref="#_label0">前言</a></li><li><ahref="#_la......
  • CommandNotFoundError: Your shell has not been properly configured to use 'conda
    当使用condaactivatemy_env激活环境时,可能会遇到如下错误:CommandNotFoundError:Yourshellhasnotbeenproperlyconfiguredtouse'condaactivate'.Toinitializeyourshell,run$condainit<SHELL_NAME>Currentlysupportedshellsare:-bash......
  • Perl 和 python 的特点和优缺点
    Perl和Python都是流行的脚本语言,它们在许多方面都有不同的优缺点。Perl的优点:强大的文本处理能力:Perl是一种专门设计用于文本处理的语言,具有强大的正则表达式支持和文本处理功能,使其在处理字符串和文件时非常高效。CPAN生态系统:Perl拥有庞大而活跃的CPAN(Comprehe......
  • openGauss 支持HyperLogLog
    支持HyperLogLog可获得性本特性自openGauss1.1.0版本开始引入。特性简介通过使用HyperLogLog相关函数,计算唯一值个数Count(Distinct),提升性能。客户价值提升AP/TP类查询的性能。特性描述HLL(HyperLogLog)是统计数据集中唯一值个数的高效近似算法。它有着计算速度快、节省空间......
  • ASP.NET中button、linkbutton、imagebutton及hyperlink这四个控件之间的功能区别?
    原文链接:https://blog.csdn.net/weixin_45763353/article/details/118005453Button是按钮控件,具有按钮所有的属性和事件方法,在客户端被渲染为表单元素提交按钮。Linkbutton是链接按钮,用于创建超链接样式的按钮。该控件的外观与HyperLink控件相同,但其功能与Button控件一样。它......
  • 从0开始教你用VScode远程连接到Linu来写代码(纯干货分享!!!新手友好篇)
     文章中汉字部分皆被代码所代替,宝宝们在复制粘贴的时候要选好对应的开源镜像站链接,详情请见评论区,我已放好相应代码!!!!!一、虚拟机的安装:安装所必须用到的软件如下:1.vmwarestation破解版(版本至少14以上)2.Xshell7破解版(最新版本即可)3.Git-bath(官方版即可)4.vscode(官方版即可......
  • 手把手教你用python一键抢12306火车票(附代码)
    哈喽,哈喽~,一年一度的抢火车票大战正式拉开序幕…然饿大多数人碰到的是这种情况:当你满心期待摩拳擦掌准备抢票的时候,你会发现一票难求!想回趟家真难!那么作为程序猿的你,当然要用程序猿的方式来抢票!下面分享用python来抢票!网站提供各种免费资源!城市cookie可根据具体需求自行添加!链......
  • 教你用Chrome 浏览器+Postman搞定接口测试
    如果把测试简单分为两类,那么就是客户端测试和服务端测试。客户端的测试包括UI测试,兼容性测试等,服务端测试包括接口测试。接口测试检查数据的交换,传递和控制管理过程,它绕过了客户端,直接对服务端进行测试。客户端测试与服务端测试的关系图:服务端非常复杂,就像下图的阿里核心链路图......
  • perl:获取同花顺数据--业绩预告
    perldoc LWP::UserAgent如果没有安装,则安装模块,运行cpanm LWP::UserAgent 。编写  get_yjyg_10jqka.pl 如下#!/usr/bin/perl#perl获取同花顺数据--业绩预告useLWP::UserAgent;useEncodeqw(decodeencode);usePOSIX;useData::Dumper;useHTML::TreeBui......