华为云短信服务教你用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协议栈里的建立连接
├── 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;
}
相关开源项目
- netty-codec-sms 存放各种SMS协议(如cmpp、sgip、smpp)的netty编解码器
- sms-client-java 存放各种SMS协议的Java客户端
- sms-server-java 存放各种SMS协议的Java服务端
- cmpp-python cmpp协议的python实现
- cngp-zig cmpp协议的python实现
- sgip-cpp sgip协议的cpp实现
- smgp-perl smgp协议的perl实现
- smpp-rust smpp协议的rust实现
总结
本文简单对SGIP协议进行了介绍,并尝试用C++实现协议栈,但实际商用发送短信往往更加复杂,面临诸如流控、运营商对接、传输层安全等问题,可以选择华为云消息&短信(Message & SMS)服务通过HTTP协议接入,华为云短信服务是华为云携手全球多家优质运营商和渠道,为企业用户提供的通信服务。企业调用API或使用群发助手,即可使用验证码、通知短信服务。
关注公众号
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
如何使用前端表格控件实现数据更新?
前言 小编之前分享过一篇文章叫《如何使用前端表格控件实现多数据源整合?》(可以放上文章的链接)。今天,继续为大家介绍如何使用前端表格控件来更新已连接的数据源信息。 环境准备 SpreadJS在线表格编辑器: SpreadJS 前端表格控件新版本新增了一款报表插件,该插件基于 SpreadJS 本身强大的表格能力,在 DataManager 数据关系引擎的助力下,全新的报表插件让报表和数据录入用户有了全新的能力和体验,快速完成报表和数据录入功能的搭建,相对之前,能节省大量的开发成本,此外,报表 还支持数据输入、分页、数据筛选、排序、条件格式化等功能。 一、设置数据源 设置数据源方式有三种:远程数据源、本地数据源、本地json文件,详细内容可以参考上一篇文章《如何使用前端表格控件实现多数据源整合?》 二、更新数据源 目前,SpreadJS支持两种方式更新数据源,分别是AutoSync和Batch模式。 上一篇系列文章介绍过通过addTable接口的remote、schema、data属性实现数据源的添加url数据源,这篇文章小编将继续介绍如何通过addTable接口的autoSync和ba...
-
下一篇
Zadig 3.0 AI 版重磅发布,超越 DevOps,解锁未来
亲爱的开发者们, 在这个充满想象与创新的儿童节,我们兴奋地宣布 Zadig 3.0 版本的重磅发布!这不只是一次简单的版本迭代,而是一次以 AI 为引擎,引领我们迈向 DevOps 新纪元的重大飞跃。 引入 AI 功能,开启智能新时代 Zadig 3.0 版本带来了 AI 的强大能力,为开发流程注入了新的活力: · AI 效能分析:智能化分析工程数据,迅速定位组织效能瓶颈,帮助决策。 · AI 环境检测:自动检测环境故障,定时通知 IM 巡检结果,确保每一次部署稳定可靠。 · 云服务集成:与 Azure/OpenAI 的无缝集成,扩展您的开发边界。 功能增强,打造更完善的开发体验 除了 AI 的引入,我们对基础模块进行了深度优化,让 Zadig 成为更加强大的开发伙伴: · 环境搜索:提升搜索效率,快速找到所需环境。 · 工作流任务:新增执行蓝鲸作业任务,让工作流程更加灵活。 · 发布计划:引入定时执行和审批通知优化,简化管理流程。 ️ 监控与自动化,智能化运维的得力助手 Zadig 3.0 强化了监控和自动化功能,为现代运维提供坚实支持: · 监控 Metrics 更新:新增监控项,...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Linux系统CentOS6、CentOS7手动修改IP地址
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- 2048小游戏-低调大师作品
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- 面试大杂烩



微信收款码
支付宝收款码