当前位置: 代码网 > 科技>人工智能>动态 > 华为云短信服务教你用C++实现Smgp协议

华为云短信服务教你用C++实现Smgp协议

2024年08月04日 动态 我要评论
本文分享自华为云社区《华为云短信服务教你用C++实现Smgp协议》,作者:张俭。 引言&协议概述 中国联合网络通信有限公司短消息网关系统接口协议(SGIP)是中国网通为实现短信业务而制定的一种通信协议,全称叫做Short Message Gateway Interface Protocol,用于在短消息网关(SMG)和服务提供商(SP)之间、短消息网关(SMG)和短消息网关(SMG)之间通信。 Perl的IO::Async模块提供了一套简洁的异步IO编程模型。 SGIP 协议基于...

本文分享自华为云社区《华为云短信服务教你用c++实现smgp协议》,作者:张俭。

引言&协议概述

中国联合网络通信有限公司短消息网关系统接口协议(sgip)是中国网通为实现短信业务而制定的一种通信协议,全称叫做short message gateway interface protocol,用于在短消息网关(smg)和服务提供商(sp)之间、短消息网关(smg)和短消息网关(smg)之间通信。

perl的io::async模块提供了一套简洁的异步io编程模型。

sgip 协议基于客户端/服务端模型工作。由客户端(短信应用,如手机,应用程序等)先和短信网关(smg short message gateway)建立起 tcp 长连接,并使用 sgip 命令与smg进行交互,实现短信的发送和接收。在sgip协议中,无需同步等待响应就可以发送下一个指令,实现者可以根据自己的需要,实现同步、异步两种消息传输模式,满足不同场景下的性能要求。

时序图

连接成功,发送短信

 
 
 

连接成功,从smgw接收到短信

 
 
 

协议帧介绍

image.png

sgip header

  • message length:长度为4字节,整个pdu的长度,包括header和body。
  • command id:长度为4字节,用于标识pdu的类型(例如,login、submit等)。
  • sequence number:长度为8字节,序列号,用来匹配请求和响应。

使用c++实现smgp协议栈里的建立连接

├── cmakelists.txt
├── examples
│   └── smgp_client_login_example.cpp
└── include
    └── sgipcpp
        ├── boundatomic.h
        ├── client.h
        ├── protocol.h
        └── impl
            ├── boundatomic.cpp
            ├── client.cpp
            └── protocol.cpp

cmakelists.txt:用来生成makefile和编译项目

examples:存放示例代码

  • smgp_client_login_example.cpp:存放smgp的login样例

include/sgipcpp:包含所有的c++头文件和实现文件

  • boundatomic.h:递增工具类,用来生成sequenceid
  • client.h:smgp定义,负责与smgp服务进行通信,例如建立连接、发送短信等
  • protocol.h:存放pdu,编解码等
  • impl/boundatomic.cpp:boundatomic类的实现
  • impl/client.cpp:client类的实现
  • impl/protocol.cpp:protocol中相关函数的实现

实现sequenceid递增

sequenceid是从1到0x7fffffff的值,使用**boundatomic**类实现递增:

头文件

#ifndef boundatomic_h
#define boundatomic_h

#include <atomic>
#include <cassert>

class boundatomic {
public:
    boundatomic(int min, int max);
    int next_val();

private:
    int min_;
    int max_;
    std::atomic<int> integer_;
};

#endif //boundatomic_h

内容

#include "sgipcpp/boundatomic.h"

boundatomic::boundatomic(int min, int max) : min_(min), max_(max), integer_(min) {
    assert(min <= max);
}

int boundatomic::next_val() {
    int current = integer_.load();
    int next;
    do {
        next = current >= max_ ? min_ : current + 1;
    } while (!integer_.compare_exchange_strong(current, next));

    return next;
}

实现smgp pdu以及编解码函数

在**protocol.h**中定义smgp pdu以及编解码函数:

头文件

#ifndef protocol_h
#define protocol_h

#include <cstdint>
#include <vector>

constexpr uint32_t sgip_bind = 0x00000001;
constexpr uint32_t sgip_bind_resp = 0x80000001;
constexpr uint32_t sgip_unbind = 0x00000002;
constexpr uint32_t sgip_unbind_resp = 0x80000002;
constexpr uint32_t sgip_submit = 0x00000003;
constexpr uint32_t sgip_submit_resp = 0x80000003;
constexpr uint32_t sgip_deliver = 0x00000004;
constexpr uint32_t sgip_deliver_resp = 0x80000004;
constexpr uint32_t sgip_report = 0x00000005;
constexpr uint32_t sgip_report_resp = 0x80000005;
constexpr uint32_t sgip_addsp = 0x00000006;
constexpr uint32_t sgip_addsp_resp = 0x80000006;
constexpr uint32_t sgip_modifysp = 0x00000007;
constexpr uint32_t sgip_modifysp_resp = 0x80000007;
constexpr uint32_t sgip_deletesp = 0x00000008;
constexpr uint32_t sgip_deletesp_resp = 0x80000008;
constexpr uint32_t sgip_queryroute = 0x00000009;
constexpr uint32_t sgip_queryroute_resp = 0x80000009;
constexpr uint32_t sgip_addteleseg = 0x0000000a;
constexpr uint32_t sgip_addteleseg_resp = 0x8000000a;
constexpr uint32_t sgip_modifyteleseg = 0x0000000b;
constexpr uint32_t sgip_modifyteleseg_resp = 0x8000000b;
constexpr uint32_t sgip_deleteteleseg = 0x0000000c;
constexpr uint32_t sgip_deleteteleseg_resp = 0x8000000c;
constexpr uint32_t sgip_addsmg = 0x0000000d;
constexpr uint32_t sgip_addsmg_resp = 0x8000000d;
constexpr uint32_t sgip_modifysmg = 0x0000000e;
constexpr uint32_t sgip_modifysmg_resp = 0x8000000e;
constexpr uint32_t sgip_deletesmg = 0x0000000f;
constexpr uint32_t sgip_deletesmg_resp = 0x8000000f;
constexpr uint32_t sgip_checkuser = 0x00000010;
constexpr uint32_t sgip_checkuser_resp = 0x80000010;
constexpr uint32_t sgip_userrpt = 0x00000011;
constexpr uint32_t sgip_userrpt_resp = 0x80000011;
constexpr uint32_t sgip_trace = 0x00001000;
constexpr uint32_t sgip_trace_resp = 0x80001000;

struct header {
    uint32_t total_length;
    uint32_t command_id;
    uint64_t sequence_number;
};

struct bind {
    char login_type;
    char login_name[16];
    char login_passwd[16];
    char reserve[8];
};

struct bindresp {
    char result;
    char reserve[8];
};

struct pdu {
    header header;
    union {
        bind bind;
        bindresp bind_resp;
    };
};

size_t lengthbind();
std::vector<uint8_t> encodepdu(const pdu& pdu);
pdu decodepdu(const std::vector<uint8_t>& buffer);

#endif //protocol_h

内容

#include "sgipcpp/protocol.h"
#include <cstring>
#include <ostream>
#include <stdexcept>
#include <sys/_endian.h>

size_t lengthbind(const bind& bind) {
    return 1 + 16 + 16 + 8;
}

void encodebind(const bind& bind, std::vector<uint8_t>& buffer) {
    size_t offset = 16;

    buffer[offset++] = bind.login_type;
    std::memcpy(buffer.data() + offset, bind.login_name, 16);
    offset += 16;
    std::memcpy(buffer.data() + offset, bind.login_passwd, 16);
    offset += 16;
    std::memcpy(buffer.data() + offset, bind.reserve, 8);
}

bindresp decodebindresp(const std::vector<uint8_t>& buffer) {
    bindresp bindresp;

    size_t offset = 0;

    offset += sizeof(uint32_t);
    offset += sizeof(uint32_t);

    bindresp.result = buffer[offset++];
    std::memcpy(bindresp.reserve, buffer.data() + offset, sizeof(bindresp.reserve));

    return bindresp;
}

std::vector<uint8_t> encodepdu(const pdu& pdu) {
    size_t body_length;
    switch (pdu.header.command_id) {
        case sgip_bind:
            body_length = lengthbind(pdu.bind);
            break;
        default:
            throw std::runtime_error("unsupported command id for encoding");
    }

    std::vector<uint8_t> buffer(body_length + 16);
    uint32_t total_length = htonl(body_length + 16);
    std::memcpy(buffer.data(), &total_length, 4);

    uint32_t command_id = htonl(pdu.header.command_id);
    std::memcpy(buffer.data() + 4, &command_id, 4);

    uint32_t sequence_number = htonl(pdu.header.sequence_number);
    std::memcpy(buffer.data() + 8, &sequence_number, 8);

    switch (pdu.header.command_id) {
        case sgip_bind:
            encodebind(pdu.bind, buffer);
        break;
        default:
            throw std::runtime_error("unsupported command id for encoding");
    }

    return buffer;
}

pdu decodepdu(const std::vector<uint8_t>& buffer) {
    pdu pdu;

    uint32_t command_id;
    std::memcpy(&command_id, buffer.data(), 4);
    pdu.header.command_id = ntohl(command_id);

    uint64_t sequence_number;
    std::memcpy(&sequence_number, buffer.data() + 8, 8);
    pdu.header.sequence_number = ntohl(sequence_number);

    switch (pdu.header.command_id) {
        case sgip_bind_resp:
            pdu.bind_resp = decodebindresp(buffer);
            break;
        default:
            throw std::runtime_error("unsupported command id for decoding");
    }

    return pdu;
}

实现客户端和登录方法

在**client**中实现客户端和登录方法:

头文件

#ifndef client_h
#define client_h

#include "boundatomic.h"
#include "protocol.h"
#include "asio.hpp"
#include <string>

class client {
public:
    client(const std::string& host, uint16_t port);
    ~client();

    void connect();
    bindresp bind(const bind& bind_request);
    void close();

private:
    std::string host_;
    uint16_t port_;
    asio::io_context io_context_;
    asio::ip::tcp::socket socket_;
    boundatomic* sequence_number_;

    void send(const std::vector<uint8_t>& data);
    std::vector<uint8_t> receive(size_t length);
};

#endif //client_h

内容

#include "sgipcpp/client.h"
#include <iostream>

client::client(const std::string& host, uint16_t port)
    : host_(host), port_(port), socket_(io_context_) {
    sequence_number_ = new boundatomic(1, 0x7fffffff);
}

client::~client() {
    close();
    delete sequence_number_;
}

void client::connect() {
    asio::ip::tcp::resolver resolver(io_context_);
    asio::connect(socket_, resolver.resolve(host_, std::to_string(port_)));
}

bindresp client::bind(const bind& bind_request) {
    pdu pdu;
    pdu.header.total_length = sizeof(bind) + sizeof(header);
    pdu.header.command_id = sgip_bind;
    pdu.header.sequence_number = sequence_number_->next_val();
    pdu.bind = bind_request;

    send(encodepdu(pdu));

    auto length_data = receive(4);
    uint32_t total_length = ntohl(*reinterpret_cast<uint32_t*>(length_data.data()));

    auto resp_data = receive(total_length - 4);
    pdu resp_pdu = decodepdu(resp_data);
    return resp_pdu.bind_resp;
}

void client::close() {
    socket_.close();
}

void client::send(const std::vector<uint8_t>& data) {
    asio::write(socket_, asio::buffer(data));
}

std::vector<uint8_t> client::receive(size_t length) {
    std::vector<uint8_t> buffer(length);
    asio::read(socket_, asio::buffer(buffer));
    return buffer;
}

运行example,验证连接成功

#include "sgipcpp/client.h"
#include <iostream>

int main() {
    try {
        client client("127.0.0.1", 8801);

        client.connect();
        std::cout << "connected to the server." << std::endl;

        bind bindrequest;
        bindrequest.login_type = 1;
        std::string login_name = "1234567890123456";
        std::string login_password = "1234567890123456";
        std::string reserve = "12345678";
        std::copy(login_name.begin(), login_name.end(), bindrequest.login_name);
        std::copy(login_password.begin(), login_password.end(), bindrequest.login_passwd);
        std::copy(reserve.begin(), reserve.end(), bindrequest.reserve);

        bindresp response = client.bind(bindrequest);
        if (response.result == 0) {
            std::cout << "login successful." << std::endl;
        } else {
            std::cout << "login failed with result code: " << static_cast<int>(response.result) << std::endl;
        }

        client.close();
        std::cout << "connection closed." << std::endl;

    } catch (const std::exception& e) {
        std::cerr << "error: " << e.what() << std::endl;
    }

    return 0;
}

image.png

相关开源项目

总结

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

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

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com