目录

一、原理及工具

二、实验流程及相关源码

(1)设置ip和端口号

(2)设置从站id

(3)查看报文

(4)源码及测试

三、效果


一、原理及工具

Modbus Slaves软件的使用:传送门

Modbus 协议原理:传送门

Modbus poll 和 Modbus slave调试工具下载:传送门

二、实验流程及相关源码

(1)设置ip和端口号

打开Modbus slave进行相关设置(注意这里IP和端口号可以自己进行相应设置),具体如下:

(2)设置从站id

初始显示地址(Address)和显示行数(Quantity)及表格行数(Rows)

(3)查看报文

在Display-》Communication中可以看到发送(Tx)和接收(Rx)的报文

(4)源码及测试

Modbus从站读写寄存器相关API函数如下,注意下面的代码只能读正数不能读负数,若想读负数请自行脑补或到文末下载代码:

/* modbus.h*/#ifndef MODBUSPP_MODBUS_H
#define MODBUSPP_MODBUS_H#include <cstring>
#include <stdint.h>
#include <string>#ifdef ENABLE_MODBUSPP_LOGGING
#include <cstdio>
#define LOG(fmt, ...) printf("[ modbuspp ]" fmt, ##__VA_ARGS__)
#else
#define LOG(...) (void)0
#endif#ifdef _WIN32
// WINDOWS socket
#define _WINSOCK_DEPRECATED_NO_WARNINGS#include <winsock2.h>
#pragma comment(lib, "Ws2_32.lib")
using X_SOCKET = SOCKET;
using ssize_t = int;#define X_ISVALIDSOCKET(s) ((s) != INVALID_SOCKET)
#define X_CLOSE_SOCKET(s) closesocket(s)
#define X_ISCONNECTSUCCEED(s) ((s) != SOCKET_ERROR)#else
// Berkeley socket
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using X_SOCKET = int;#define X_ISVALIDSOCKET(s) ((s) >= 0)
#define X_CLOSE_SOCKET(s) close(s)
#define X_ISCONNECTSUCCEED(s) ((s) >= 0)
#endifusing SOCKADDR = struct sockaddr;
using SOCKADDR_IN = struct sockaddr_in;#define MAX_MSG_LENGTH 260///Function Code
#define READ_COILS 0x01
#define READ_INPUT_BITS 0x02
#define READ_REGS 0x03
#define READ_INPUT_REGS 0x04
#define WRITE_COIL 0x05
#define WRITE_REG 0x06
#define WRITE_COILS 0x0F
#define WRITE_REGS 0x10///Exception Codes#define EX_ILLEGAL_FUNCTION 0x01 // Function Code not Supported
#define EX_ILLEGAL_ADDRESS 0x02  // Output Address not exists
#define EX_ILLEGAL_VALUE 0x03    // Output Value not in Range
#define EX_SERVER_FAILURE 0x04   // Slave Deive Fails to process request
#define EX_ACKNOWLEDGE 0x05      // Service Need Long Time to Execute
#define EX_SERVER_BUSY 0x06      // Server Was Unable to Accept MB Request PDU
#define EX_NEGATIVE_ACK 0x07
#define EX_MEM_PARITY_PROB 0x08
#define EX_GATEWAY_PROBLEMP 0x0A // Gateway Path not Available
#define EX_GATEWYA_PROBLEMF 0x0B // Target Device Failed to Response
#define EX_BAD_DATA 0XFF         // Bad Data lenght or Address#define BAD_CON -1/// Modbus Operator Class
/*** Modbus Operator Class* Providing networking support and mobus operation support.*/
class modbus
{public:bool err{};int err_no{};std::string error_msg;modbus(std::string host, uint16_t port);~modbus();bool modbus_connect();void modbus_close() const;void modbus_set_slave_id(int id);int modbus_read_coils(uint16_t address, uint16_t amount, bool *buffer);int modbus_read_input_bits(uint16_t address, uint16_t amount, bool *buffer);int modbus_read_holding_registers(uint16_t address, uint16_t amount, uint16_t *buffer);int modbus_read_input_registers(uint16_t address, uint16_t amount, uint16_t *buffer);int modbus_write_coil(uint16_t address, const bool &to_write);int modbus_write_register(uint16_t address, const uint16_t &value);int modbus_write_coils(uint16_t address, uint16_t amount, const bool *value);int modbus_write_registers(uint16_t address, uint16_t amount, const uint16_t *value);private:bool _connected{};uint16_t PORT{};uint32_t _msg_id{};int _slaveid{};std::string HOST;X_SOCKET _socket{};SOCKADDR_IN _server{};#ifdef _WIN32WSADATA wsadata;
#endifinline void modbus_build_request(uint8_t *to_send, uint16_t address, int func) const;int modbus_read(uint16_t address, uint16_t amount, int func);int modbus_write(uint16_t address, uint16_t amount, int func, const uint16_t *value);inline ssize_t modbus_send(uint8_t *to_send, size_t length);inline ssize_t modbus_receive(uint8_t *buffer) const;void modbuserror_handle(const uint8_t *msg, int func);inline void set_bad_con();inline void set_bad_input();
};/*** Main Constructor of Modbus Connector Object* @param host IP Address of Host* @param port Port for the TCP Connection* @return     A Modbus Connector Object*/
modbus::modbus(std::string host, uint16_t port = 502)
{HOST = host;PORT = port;_slaveid = 1;_msg_id = 1;_connected = false;err = false;err_no = 0;error_msg = "";
}/*** Destructor of Modbus Connector Object*/
modbus::~modbus(void) = default;/*** Modbus Slave ID Setter* @param id  ID of the Modbus Server Slave*/
void modbus::modbus_set_slave_id(int id)
{_slaveid = id;
}/*** Build up a Modbus/TCP Connection* @return   If A Connection Is Successfully Built*/
bool modbus::modbus_connect()
{if (HOST.empty() || PORT == 0){LOG("Missing Host and Port");return false;}else{LOG("Found Proper Host %s and Port %d", HOST.c_str(), PORT);}#ifdef _WIN32if (WSAStartup(0x0202, &wsadata)){return false;}
#endif_socket = socket(AF_INET, SOCK_STREAM, 0);if (!X_ISVALIDSOCKET(_socket)){LOG("Error Opening Socket");
#ifdef _WIN32WSACleanup();
#endifreturn false;}else{LOG("Socket Opened Successfully");}#ifdef WIN32const DWORD timeout = 20;
#elsestruct timeval timeout{};timeout.tv_sec = 20; // after 20 seconds connect() will timeouttimeout.tv_usec = 0;
#endifsetsockopt(_socket, SOL_SOCKET, SO_SNDTIMEO, (const char *)&timeout, sizeof(timeout));setsockopt(_socket, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(timeout));_server.sin_family = AF_INET;_server.sin_addr.s_addr = inet_addr(HOST.c_str());_server.sin_port = htons(PORT);if (!X_ISCONNECTSUCCEED(connect(_socket, (SOCKADDR *)&_server, sizeof(_server)))){LOG("Connection Error");
#ifdef _WIN32WSACleanup();
#endifreturn false;}LOG("Connected");_connected = true;return true;
}/*** Close the Modbus/TCP Connection*/
void modbus::modbus_close() const
{X_CLOSE_SOCKET(_socket);
#ifdef _WIN32WSACleanup();
#endifLOG("Socket Closed");
}/*** Modbus Request Builder* @param to_send   Message Buffer to Be Sent* @param address   Reference Address* @param func      Modbus Functional Code*/
void modbus::modbus_build_request(uint8_t *to_send, uint16_t address, int func) const
{to_send[0] = (uint8_t)(_msg_id >> 8u);to_send[1] = (uint8_t)(_msg_id & 0x00FFu);to_send[2] = 0;to_send[3] = 0;to_send[4] = 0;to_send[6] = (uint8_t)_slaveid;to_send[7] = (uint8_t)func;to_send[8] = (uint8_t)(address >> 8u);to_send[9] = (uint8_t)(address & 0x00FFu);
}/*** Write Request Builder and Sender* @param address   Reference Address* @param amount    Amount of data to be Written* @param func      Modbus Functional Code* @param value     Data to Be Written*/
int modbus::modbus_write(uint16_t address, uint16_t amount, int func, const uint16_t *value)
{int status = 0;uint8_t *to_send;if (func == WRITE_COIL || func == WRITE_REG){to_send = new uint8_t[12];modbus_build_request(to_send, address, func);to_send[5] = 6;to_send[10] = (uint8_t)(value[0] >> 8u);to_send[11] = (uint8_t)(value[0] & 0x00FFu);status = modbus_send(to_send, 12);}else if (func == WRITE_REGS){to_send = new uint8_t[13 + 2 * amount];modbus_build_request(to_send, address, func);to_send[5] = (uint8_t)(7 + 2 * amount);to_send[10] = (uint8_t)(amount >> 8u);to_send[11] = (uint8_t)(amount & 0x00FFu);to_send[12] = (uint8_t)(2 * amount);for (int i = 0; i < amount; i++){to_send[13 + 2 * i] = (uint8_t)(value[i] >> 8u);to_send[14 + 2 * i] = (uint8_t)(value[i] & 0x00FFu);}status = modbus_send(to_send, 13 + 2 * amount);}else if (func == WRITE_COILS){to_send = new uint8_t[14 + (amount - 1) / 8];modbus_build_request(to_send, address, func);to_send[5] = (uint8_t)(7 + (amount + 7) / 8);to_send[10] = (uint8_t)(amount >> 8u);to_send[11] = (uint8_t)(amount & 0x00FFu);to_send[12] = (uint8_t)((amount + 7) / 8);for (int i = 0; i < (amount + 7) / 8; i++)to_send[13 + i] = 0; // init needed before summing!for (int i = 0; i < amount; i++){to_send[13 + i / 8] += (uint8_t)(value[i] << (i % 8u));}status = modbus_send(to_send, 14 + (amount - 1) / 8);}delete[] to_send;return status;
}/*** Read Request Builder and Sender* @param address   Reference Address* @param amount    Amount of Data to Read* @param func      Modbus Functional Code*/
int modbus::modbus_read(uint16_t address, uint16_t amount, int func)
{uint8_t to_send[12];modbus_build_request(to_send, address, func);to_send[5] = 6;to_send[10] = (uint8_t)(amount >> 8u);to_send[11] = (uint8_t)(amount & 0x00FFu);return modbus_send(to_send, 12);
}/*** Read Holding Registers* MODBUS FUNCTION 0x03* @param address    Reference Address* @param amount     Amount of Registers to Read* @param buffer     Buffer to Store Data Read from Registers*/
int modbus::modbus_read_holding_registers(uint16_t address, uint16_t amount, uint16_t *buffer)
{if (_connected){modbus_read(address, amount, READ_REGS);uint8_t to_rec[MAX_MSG_LENGTH];ssize_t k = modbus_receive(to_rec);if (k == -1){set_bad_con();return BAD_CON;}modbuserror_handle(to_rec, READ_REGS);if (err)return err_no;for (auto i = 0; i < amount; i++){buffer[i] = ((uint16_t)to_rec[9u + 2u * i]) << 8u;buffer[i] += (uint16_t)to_rec[10u + 2u * i];}return 0;}else{set_bad_con();return BAD_CON;}
}/*** Read Input Registers* MODBUS FUNCTION 0x04* @param address     Reference Address* @param amount      Amount of Registers to Read* @param buffer      Buffer to Store Data Read from Registers*/
int modbus::modbus_read_input_registers(uint16_t address, uint16_t amount, uint16_t *buffer)
{if (_connected){modbus_read(address, amount, READ_INPUT_REGS);uint8_t to_rec[MAX_MSG_LENGTH];ssize_t k = modbus_receive(to_rec);if (k == -1){set_bad_con();return BAD_CON;}modbuserror_handle(to_rec, READ_INPUT_REGS);if (err)return err_no;for (auto i = 0; i < amount; i++){buffer[i] = ((uint16_t)to_rec[9u + 2u * i]) << 8u;buffer[i] += (uint16_t)to_rec[10u + 2u * i];}return 0;}else{set_bad_con();return BAD_CON;}
}/*** Read Coils* MODBUS FUNCTION 0x01* @param address     Reference Address* @param amount      Amount of Coils to Read* @param buffer      Buffer to Store Data Read from Coils*/
int modbus::modbus_read_coils(uint16_t address, uint16_t amount, bool *buffer)
{if (_connected){if (amount > 2040){set_bad_input();return EX_BAD_DATA;}modbus_read(address, amount, READ_COILS);uint8_t to_rec[MAX_MSG_LENGTH];ssize_t k = modbus_receive(to_rec);if (k == -1){set_bad_con();return BAD_CON;}modbuserror_handle(to_rec, READ_COILS);if (err)return err_no;for (auto i = 0; i < amount; i++){buffer[i] = (bool)((to_rec[9u + i / 8u] >> (i % 8u)) & 1u);}return 0;}else{set_bad_con();return BAD_CON;}
}/*** Read Input Bits(Discrete Data)* MODBUS FUNCITON 0x02* @param address   Reference Address* @param amount    Amount of Bits to Read* @param buffer    Buffer to store Data Read from Input Bits*/
int modbus::modbus_read_input_bits(uint16_t address, uint16_t amount, bool *buffer)
{if (_connected){if (amount > 2040){set_bad_input();return EX_BAD_DATA;}modbus_read(address, amount, READ_INPUT_BITS);uint8_t to_rec[MAX_MSG_LENGTH];ssize_t k = modbus_receive(to_rec);if (k == -1){set_bad_con();return BAD_CON;}if (err)return err_no;for (auto i = 0; i < amount; i++){buffer[i] = (bool)((to_rec[9u + i / 8u] >> (i % 8u)) & 1u);}modbuserror_handle(to_rec, READ_INPUT_BITS);return 0;}else{return BAD_CON;}
}/*** Write Single Coils* MODBUS FUNCTION 0x05* @param address    Reference Address* @param to_write   Value to be Written to Coil*/
int modbus::modbus_write_coil(uint16_t address, const bool &to_write)
{if (_connected){int value = to_write * 0xFF00;modbus_write(address, 1, WRITE_COIL, (uint16_t *)&value);uint8_t to_rec[MAX_MSG_LENGTH];ssize_t k = modbus_receive(to_rec);if (k == -1){set_bad_con();return BAD_CON;}modbuserror_handle(to_rec, WRITE_COIL);if (err)return err_no;return 0;}else{set_bad_con();return BAD_CON;}
}/*** Write Single Register* FUCTION 0x06* @param address   Reference Address* @param value     Value to Be Written to Register*/
int modbus::modbus_write_register(uint16_t address, const uint16_t &value)
{if (_connected){modbus_write(address, 1, WRITE_REG, &value);uint8_t to_rec[MAX_MSG_LENGTH];ssize_t k = modbus_receive(to_rec);if (k == -1){set_bad_con();return BAD_CON;}modbuserror_handle(to_rec, WRITE_COIL);if (err)return err_no;return 0;}else{set_bad_con();return BAD_CON;}
}/*** Write Multiple Coils* MODBUS FUNCTION 0x0F* @param address  Reference Address* @param amount   Amount of Coils to Write* @param value    Values to Be Written to Coils*/
int modbus::modbus_write_coils(uint16_t address, uint16_t amount, const bool *value)
{if (_connected){uint16_t *temp = new uint16_t[amount];for (int i = 0; i < amount; i++){temp[i] = (uint16_t)value[i];}modbus_write(address, amount, WRITE_COILS, temp);delete[] temp;uint8_t to_rec[MAX_MSG_LENGTH];ssize_t k = modbus_receive(to_rec);if (k == -1){set_bad_con();return BAD_CON;}modbuserror_handle(to_rec, WRITE_COILS);if (err)return err_no;return 0;}else{set_bad_con();return BAD_CON;}
}/*** Write Multiple Registers* MODBUS FUNCION 0x10* @param address Reference Address* @param amount  Amount of Value to Write* @param value   Values to Be Written to the Registers*/
int modbus::modbus_write_registers(uint16_t address, uint16_t amount, const uint16_t *value)
{if (_connected){modbus_write(address, amount, WRITE_REGS, value);uint8_t to_rec[MAX_MSG_LENGTH];ssize_t k = modbus_receive(to_rec);if (k == -1){set_bad_con();return BAD_CON;}modbuserror_handle(to_rec, WRITE_REGS);if (err)return err_no;return 0;}else{set_bad_con();return BAD_CON;}
}/*** Data Sender* @param to_send Request to Be Sent to Server* @param length  Length of the Request* @return        Size of the request*/
ssize_t modbus::modbus_send(uint8_t *to_send, size_t length)
{_msg_id++;return send(_socket, (const char *)to_send, (size_t)length, 0);
}/*** Data Receiver* @param buffer Buffer to Store the Data Retrieved* @return       Size of Incoming Data*/
ssize_t modbus::modbus_receive(uint8_t *buffer) const
{return recv(_socket, (char *)buffer, 1024, 0);
}void modbus::set_bad_con()
{err = true;error_msg = "BAD CONNECTION";
}void modbus::set_bad_input()
{err = true;error_msg = "BAD FUNCTION INPUT";
}/*** Error Code Handler* @param msg   Message Received from the Server* @param func  Modbus Functional Code*/
void modbus::modbuserror_handle(const uint8_t *msg, int func)
{err = false;error_msg = "NO ERR";if (msg[7] == func + 0x80){err = true;switch (msg[8]){case EX_ILLEGAL_FUNCTION:error_msg = "1 Illegal Function";break;case EX_ILLEGAL_ADDRESS:error_msg = "2 Illegal Address";break;case EX_ILLEGAL_VALUE:error_msg = "3 Illegal Value";break;case EX_SERVER_FAILURE:error_msg = "4 Server Failure";break;case EX_ACKNOWLEDGE:error_msg = "5 Acknowledge";break;case EX_SERVER_BUSY:error_msg = "6 Server Busy";break;case EX_NEGATIVE_ACK:error_msg = "7 Negative Acknowledge";break;case EX_MEM_PARITY_PROB:error_msg = "8 Memory Parity Problem";break;case EX_GATEWAY_PROBLEMP:error_msg = "10 Gateway Path Unavailable";break;case EX_GATEWYA_PROBLEMF:error_msg = "11 Gateway Target Device Failed to Respond";break;default:error_msg = "UNK";break;}}
}#endif //MODBUSPP_MODBUS_H

测试代码如下:

/*test.cpp
*/
#include "modbus.h"int main(int argc, char **argv)
{// create a modbus objectmodbus mb = modbus("127.0.0.1", 502);// set slave idmb.modbus_set_slave_id(1);// connect with the servermb.modbus_connect();// read coil                        function 0x01bool read_coil;mb.modbus_read_coils(0, 1, &read_coil);// read input bits(discrete input)  function 0x02bool read_bits;mb.modbus_read_input_bits(0, 1, &read_bits);// read holding registers           function 0x03uint16_t read_holding_regs[1];mb.modbus_read_holding_registers(0, 1, read_holding_regs);// read input registers             function 0x04uint16_t read_input_regs[1];mb.modbus_read_input_registers(0, 1, read_input_regs);// write single coil                function 0x05mb.modbus_write_coil(0, true);// write single reg                 function 0x06mb.modbus_write_register(0, 123);// write multiple coils             function 0x0Fbool write_cols[4] = {true, true, true, true};mb.modbus_write_coils(0, 4, write_cols);// write multiple regs              function 0x10uint16_t write_regs[4] = {123, 123, 123};mb.modbus_write_registers(0, 4, write_regs);// close connection and free the memorymb.modbus_close();return 0;
}

三、效果

含有负数数据的读写:传送门

Modbus从站读写数据相关推荐

  1. S7300以太网模块作为Modbus从站 实现PLC与其它Modbus 设备的通讯

    远创智控MPI-ETH-YC01Puls转以太网模块支持 Modbus 功能,可作为 Modbus 从站,实现 PLC 与其他 Modbus 设备的通讯. 一.   Modbus 从站功能介绍 1.1 ...

  2. 工业自动化MODBUS协议读写器读卡器配置软件|工具之读写卡模式配置操作攻略

    本程序MODBUS读卡器配置软件所支持配置的读卡模式有四种,分别为标准MODBUS.Enchance Mode.Continue Read Mode和Read Once Mode.具体使用何种读卡模式 ...

  3. 西门子串口通讯08-CP341在STEP7环境中做Modbus从站通讯

    西门子串口通讯08-CP341在STEP7环境中做Modbus从站通讯 v-x-公–众–号:工控自动化老王 1 硬件列表 设备名称 设备型号 PS 307 6ES7 307-1EA00-0AA0 CP ...

  4. 西门子串口通讯09-CP341在博途环境中做Modbus从站通讯

    西门子串口通讯09-CP341在博途环境中做Modbus从站通讯 v-x-公–众–号:工控自动化老王 1 硬件列表 设备名称 设备型号 PS 307 6ES7 307-1EA01-0AA0 CPU 3 ...

  5. 西门子串口通讯10-CP441-2在STEP7环境中做Modbus从站通讯

    西门子串口通讯10-CP441-2在STEP7环境中做Modbus从站通讯 v-x-公–众–号:工控自动化老王 2) CP441-2与调试软件测试通信数据 1 硬件列表 设备名称 设备型号 RACK- ...

  6. 西门子串口通讯11-CP441-2在博图环境中做Modbus从站通讯

    CP441-2 使用 TIA Step7 V13 进行 Modbus Slave 通讯 作者:工控自动化老王 关注同名V–X公//众//号:工控自动化老王获取更多技术文档及工控资源,交流.学习,另外老 ...

  7. MODBUS通讯之数据帧格式解读(附资料下载)

    MODBUS通讯之数据帧格式解读(文末可免费下载文档) 一.背景 之前在一个项目上用代码分别实现了Modbus主站和Modbus从站(注:其实官方提供有现成的MODBUS从站库代码,并且支持大多数的嵌 ...

  8. modbus从站模拟软件_这些操作软件都不知道?趁早别当电气人了

    作为工控电气人,你知道我们必备的软件有哪些吗?今天我就来给大家介绍一下,工控电气人常用的几款软件,有了它们,我们的工作学习将会更易上手,效率翻倍.以下介绍主要是分为电工常用软件,PLC编程软件,工控辅 ...

  9. 【Android 高性能音频】AAudio 音频流 读写操作 ( 音频流读写数据 | 阻塞时间设定 | 注意事项 | AAudioStream_read | AAudioStream_write )

    文章目录 I . AAudio 音频流 读写操作 简介 II . AAudio 音频流 读写操作 阻塞时间设定 III . AAudio 音频流 读取 固定帧数 操作 注意点 IV . AAudio ...

  10. android json mysql_Android通过json向MySQL中读写数据的方法详解【读取篇】

    本文实例讲述了Android通过json向MySQL中读取数据的方法.分享给大家供大家参考,具体如下: 首先 要定义几个解析json的方法parseJsonMulti,代码如下: private vo ...

最新文章

  1. JAVA中List的几个方法
  2. 岗位推荐 | 百度招聘计算机视觉、深度学习算法工程师(可实习)
  3. 人工机器:作为归纳系统的深度学习
  4. 【转】1.6异步编程:IAsyncResult异步编程模型 (APM)
  5. 原创 | 开源AI测试专题、Jmeter测试专题
  6. 1.9编程基础之顺序查找 01查找特定的值
  7. 取消文件与svn服务器的关联
  8. 疯狂Android讲义
  9. DLL注入 + VEH 的方式处理异常
  10. 又要放大招了 监控中干扰如何消除你可知道?
  11. C++ 读取文件时报错“将一个无效参数传递给了将无效参数视为严重错误的函数”解决方法
  12. vue 表单验证正则_vue表单验证
  13. left out join举例
  14. 计算机技术在文物修复中的应用,【3D打印技术在文物修复中的应用原稿材料】...
  15. 用于威胁情报分析的虚拟机
  16. Qt+openCV学习笔记(五)Qt5.15.2+openCV4.5.4+VS2019_64编译动态库
  17. 现在我们家BB 28周了
  18. [脑洞]使用annotation生成反射常量池
  19. macos 读取ntfs
  20. 2011夏,桂林阳朔龙脊详细攻略 游记

热门文章

  1. 推荐一个视频网站-播布客
  2. oracle 安装service pack是什么,安装 service pack 和修补程序 | Microsoft Docs
  3. python tensorflow 文本提取_用RNN构建文本生成器(TensorFlow Eager+ tf.keras)
  4. [渝粤教育] 九江职业技术学院 客户关系管理 参考 资料
  5. Apache Pulsar 生态项目 AoP 新增两位中国移动 Maintainer!
  6. 这个被上帝抛弃的国家,创立了全球一半的科技公司
  7. Debian11新装系统美化
  8. python文件加密
  9. 构建Spring Web应用程序
  10. 搭建个人博客【搭建Hexo+Fluid博客并部署到GitHub/云服务器(阿里云/腾讯云)】