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

本文分享自华为云社区《华为云短信服务教你用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协议中,无需同步等待响应就可以发送下一个指令,实现者可以根据自己的需要,实现同步、异步两种消息传输模式,满足不同场景下的性能要求。

时序图

连接成功,发送短信

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

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

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

协议帧介绍

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

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; }

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

相关开源项目

总结

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

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

 

发表评论

评论已关闭。

相关文章

当前内容话题
  • 0