当前位置:   article > 正文

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

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

时序图

连接成功,发送短信
 

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

协议帧介绍

SGIP Header

  • Message Length:长度为4字节,整个PDU的长度,包括Header和Body。
  • Command ID:长度为4字节,用于标识PDU的类型(例如,Login、Submit等)。
  • Sequence Number:长度为8字节,序列号,用来匹配请求和响应。

使用C++实现SMGP协议栈里的建立连接

  1. ├── CMakeLists.txt
  2. ├── examples
  3. │ └── smgp_client_login_example.cpp
  4. └── include
  5. └── sgipcpp
  6. ├── BoundAtomic.h
  7. ├── Client.h
  8. ├── Protocol.h
  9. └── impl
  10. ├── BoundAtomic.cpp
  11. ├── Client.cpp
  12. └── 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**类实现递增:

头文件

  1. #ifndef BOUNDATOMIC_H
  2. #define BOUNDATOMIC_H
  3. #include <atomic>
  4. #include <cassert>
  5. class BoundAtomic {
  6. public:
  7. BoundAtomic(int min, int max);
  8. int next_val();
  9. private:
  10. int min_;
  11. int max_;
  12. std::atomic<int> integer_;
  13. };
  14. #endif //BOUNDATOMIC_H

内容

  1. #include "sgipcpp/BoundAtomic.h"
  2. BoundAtomic::BoundAtomic(int min, int max) : min_(min), max_(max), integer_(min) {
  3. assert(min <= max);
  4. }
  5. int BoundAtomic::next_val() {
  6. int current = integer_.load();
  7. int next;
  8. do {
  9. next = current >= max_ ? min_ : current + 1;
  10. } while (!integer_.compare_exchange_strong(current, next));
  11. return next;
  12. }

实现SMGP PDU以及编解码函数

在**Protocol.h**中定义SMGP PDU以及编解码函数:

头文件

  1. #ifndef PROTOCOL_H
  2. #define PROTOCOL_H
  3. #include <cstdint>
  4. #include <vector>
  5. constexpr uint32_t SGIP_BIND = 0x00000001;
  6. constexpr uint32_t SGIP_BIND_RESP = 0x80000001;
  7. constexpr uint32_t SGIP_UNBIND = 0x00000002;
  8. constexpr uint32_t SGIP_UNBIND_RESP = 0x80000002;
  9. constexpr uint32_t SGIP_SUBMIT = 0x00000003;
  10. constexpr uint32_t SGIP_SUBMIT_RESP = 0x80000003;
  11. constexpr uint32_t SGIP_DELIVER = 0x00000004;
  12. constexpr uint32_t SGIP_DELIVER_RESP = 0x80000004;
  13. constexpr uint32_t SGIP_REPORT = 0x00000005;
  14. constexpr uint32_t SGIP_REPORT_RESP = 0x80000005;
  15. constexpr uint32_t SGIP_ADDSP = 0x00000006;
  16. constexpr uint32_t SGIP_ADDSP_RESP = 0x80000006;
  17. constexpr uint32_t SGIP_MODIFYSP = 0x00000007;
  18. constexpr uint32_t SGIP_MODIFYSP_RESP = 0x80000007;
  19. constexpr uint32_t SGIP_DELETESP = 0x00000008;
  20. constexpr uint32_t SGIP_DELETESP_RESP = 0x80000008;
  21. constexpr uint32_t SGIP_QUERYROUTE = 0x00000009;
  22. constexpr uint32_t SGIP_QUERYROUTE_RESP = 0x80000009;
  23. constexpr uint32_t SGIP_ADDTELESEG = 0x0000000A;
  24. constexpr uint32_t SGIP_ADDTELESEG_RESP = 0x8000000A;
  25. constexpr uint32_t SGIP_MODIFYTELESEG = 0x0000000B;
  26. constexpr uint32_t SGIP_MODIFYTELESEG_RESP = 0x8000000B;
  27. constexpr uint32_t SGIP_DELETETELESEG = 0x0000000C;
  28. constexpr uint32_t SGIP_DELETETELESEG_RESP = 0x8000000C;
  29. constexpr uint32_t SGIP_ADDSMG = 0x0000000D;
  30. constexpr uint32_t SGIP_ADDSMG_RESP = 0x8000000D;
  31. constexpr uint32_t SGIP_MODIFYSMG = 0x0000000E;
  32. constexpr uint32_t SGIP_MODIFYSMG_RESP = 0x8000000E;
  33. constexpr uint32_t SGIP_DELETESMG = 0x0000000F;
  34. constexpr uint32_t SGIP_DELETESMG_RESP = 0x8000000F;
  35. constexpr uint32_t SGIP_CHECKUSER = 0x00000010;
  36. constexpr uint32_t SGIP_CHECKUSER_RESP = 0x80000010;
  37. constexpr uint32_t SGIP_USERRPT = 0x00000011;
  38. constexpr uint32_t SGIP_USERRPT_RESP = 0x80000011;
  39. constexpr uint32_t SGIP_TRACE = 0x00001000;
  40. constexpr uint32_t SGIP_TRACE_RESP = 0x80001000;
  41. struct Header {
  42. uint32_t total_length;
  43. uint32_t command_id;
  44. uint64_t sequence_number;
  45. };
  46. struct Bind {
  47. char login_type;
  48. char login_name[16];
  49. char login_passwd[16];
  50. char reserve[8];
  51. };
  52. struct BindResp {
  53. char result;
  54. char reserve[8];
  55. };
  56. struct Pdu {
  57. Header header;
  58. union {
  59. Bind bind;
  60. BindResp bind_resp;
  61. };
  62. };
  63. size_t lengthBind();
  64. std::vector<uint8_t> encodePdu(const Pdu& pdu);
  65. Pdu decodePdu(const std::vector<uint8_t>& buffer);
  66. #endif //PROTOCOL_H

内容

  1. #include "sgipcpp/Protocol.h"
  2. #include <cstring>
  3. #include <ostream>
  4. #include <stdexcept>
  5. #include <sys/_endian.h>
  6. size_t lengthBind(const Bind& bind) {
  7. return 1 + 16 + 16 + 8;
  8. }
  9. void encodeBind(const Bind& bind, std::vector<uint8_t>& buffer) {
  10. size_t offset = 16;
  11. buffer[offset++] = bind.login_type;
  12. std::memcpy(buffer.data() + offset, bind.login_name, 16);
  13. offset += 16;
  14. std::memcpy(buffer.data() + offset, bind.login_passwd, 16);
  15. offset += 16;
  16. std::memcpy(buffer.data() + offset, bind.reserve, 8);
  17. }
  18. BindResp decodeBindResp(const std::vector<uint8_t>& buffer) {
  19. BindResp bindResp;
  20. size_t offset = 0;
  21. offset += sizeof(uint32_t);
  22. offset += sizeof(uint32_t);
  23. bindResp.result = buffer[offset++];
  24. std::memcpy(bindResp.reserve, buffer.data() + offset, sizeof(bindResp.reserve));
  25. return bindResp;
  26. }
  27. std::vector<uint8_t> encodePdu(const Pdu& pdu) {
  28. size_t body_length;
  29. switch (pdu.header.command_id) {
  30. case SGIP_BIND:
  31. body_length = lengthBind(pdu.bind);
  32. break;
  33. default:
  34. throw std::runtime_error("Unsupported command ID for encoding");
  35. }
  36. std::vector<uint8_t> buffer(body_length + 16);
  37. uint32_t total_length = htonl(body_length + 16);
  38. std::memcpy(buffer.data(), &total_length, 4);
  39. uint32_t command_id = htonl(pdu.header.command_id);
  40. std::memcpy(buffer.data() + 4, &command_id, 4);
  41. uint32_t sequence_number = htonl(pdu.header.sequence_number);
  42. std::memcpy(buffer.data() + 8, &sequence_number, 8);
  43. switch (pdu.header.command_id) {
  44. case SGIP_BIND:
  45. encodeBind(pdu.bind, buffer);
  46. break;
  47. default:
  48. throw std::runtime_error("Unsupported command ID for encoding");
  49. }
  50. return buffer;
  51. }
  52. Pdu decodePdu(const std::vector<uint8_t>& buffer) {
  53. Pdu pdu;
  54. uint32_t command_id;
  55. std::memcpy(&command_id, buffer.data(), 4);
  56. pdu.header.command_id = ntohl(command_id);
  57. uint64_t sequence_number;
  58. std::memcpy(&sequence_number, buffer.data() + 8, 8);
  59. pdu.header.sequence_number = ntohl(sequence_number);
  60. switch (pdu.header.command_id) {
  61. case SGIP_BIND_RESP:
  62. pdu.bind_resp = decodeBindResp(buffer);
  63. break;
  64. default:
  65. throw std::runtime_error("Unsupported command ID for decoding");
  66. }
  67. return pdu;
  68. }

实现客户端和登录方法

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

头文件

  1. #ifndef CLIENT_H
  2. #define CLIENT_H
  3. #include "BoundAtomic.h"
  4. #include "Protocol.h"
  5. #include "asio.hpp"
  6. #include <string>
  7. class Client {
  8. public:
  9. Client(const std::string& host, uint16_t port);
  10. ~Client();
  11. void connect();
  12. BindResp bind(const Bind& bind_request);
  13. void close();
  14. private:
  15. std::string host_;
  16. uint16_t port_;
  17. asio::io_context io_context_;
  18. asio::ip::tcp::socket socket_;
  19. BoundAtomic* sequence_number_;
  20. void send(const std::vector<uint8_t>& data);
  21. std::vector<uint8_t> receive(size_t length);
  22. };
  23. #endif //CLIENT_H

内容

  1. #include "sgipcpp/Client.h"
  2. #include <iostream>
  3. Client::Client(const std::string& host, uint16_t port)
  4. : host_(host), port_(port), socket_(io_context_) {
  5. sequence_number_ = new BoundAtomic(1, 0x7FFFFFFF);
  6. }
  7. Client::~Client() {
  8. close();
  9. delete sequence_number_;
  10. }
  11. void Client::connect() {
  12. asio::ip::tcp::resolver resolver(io_context_);
  13. asio::connect(socket_, resolver.resolve(host_, std::to_string(port_)));
  14. }
  15. BindResp Client::bind(const Bind& bind_request) {
  16. Pdu pdu;
  17. pdu.header.total_length = sizeof(Bind) + sizeof(Header);
  18. pdu.header.command_id = SGIP_BIND;
  19. pdu.header.sequence_number = sequence_number_->next_val();
  20. pdu.bind = bind_request;
  21. send(encodePdu(pdu));
  22. auto length_data = receive(4);
  23. uint32_t total_length = ntohl(*reinterpret_cast<uint32_t*>(length_data.data()));
  24. auto resp_data = receive(total_length - 4);
  25. Pdu resp_pdu = decodePdu(resp_data);
  26. return resp_pdu.bind_resp;
  27. }
  28. void Client::close() {
  29. socket_.close();
  30. }
  31. void Client::send(const std::vector<uint8_t>& data) {
  32. asio::write(socket_, asio::buffer(data));
  33. }
  34. std::vector<uint8_t> Client::receive(size_t length) {
  35. std::vector<uint8_t> buffer(length);
  36. asio::read(socket_, asio::buffer(buffer));
  37. return buffer;
  38. }

运行example,验证连接成功

  1. #include "sgipcpp/Client.h"
  2. #include <iostream>
  3. int main() {
  4. try {
  5. Client client("127.0.0.1", 8801);
  6. client.connect();
  7. std::cout << "Connected to the server." << std::endl;
  8. Bind bindRequest;
  9. bindRequest.login_type = 1;
  10. std::string login_name = "1234567890123456";
  11. std::string login_password = "1234567890123456";
  12. std::string reserve = "12345678";
  13. std::copy(login_name.begin(), login_name.end(), bindRequest.login_name);
  14. std::copy(login_password.begin(), login_password.end(), bindRequest.login_passwd);
  15. std::copy(reserve.begin(), reserve.end(), bindRequest.reserve);
  16. BindResp response = client.bind(bindRequest);
  17. if (response.result == 0) {
  18. std::cout << "Login successful." << std::endl;
  19. } else {
  20. std::cout << "Login failed with result code: " << static_cast<int>(response.result) << std::endl;
  21. }
  22. client.close();
  23. std::cout << "Connection closed." << std::endl;
  24. } catch (const std::exception& e) {
  25. std::cerr << "Error: " << e.what() << std::endl;
  26. }
  27. return 0;
  28. }

相关开源项目

总结

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

HDC 2024,6月21日-23日,东莞松山湖,期待与您相见!

更多详情请关注官网:

中文:华为开发者大会2024_HDC 2024

英文:华为开发者大会2024_HDC 2024

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/729512
推荐阅读
相关标签
  

闽ICP备14008679号