多个单片机简单通讯框架

文章目录

  • 一、场景描述
  • 二、框架搭建
      • 设计思路
      • 通信协议设计
      • 2号单片机通讯框架
      • 框架优化建议
  • 三、2号单片机的通讯框架如何处理消息丢失和重传?
      • 消息丢失与重传机制设计
      • 改进的通信协议
      • 重传机制实现
      • 关键机制说明
      • 优化建议

一、场景描述

有3个单片机进行通讯,分别为1号,2号,3号。
1号与2号可以通讯,2号与3号可以通讯。

那么如果1号想通过2号,与3号进行通讯。

如何搭建2号的通讯框架,保证1号的通讯能够及时给3号,同时3号也能及时应答给1号。使用串口通讯,协议自定

二、框架搭建

设计思路

为了实现三个单片机之间的可靠中继通信,2号单片机需要设计一个高效的通讯框架。关键在于如何处理串口接收、消息转发和应答机制,同时保证消息的实时性和可靠性。

通信协议设计

首先需要定义一个简单但功能完整的通信协议:

消息格式: [SOF][源地址][目标地址][消息ID][长度][数据][CRC]- SOF: 起始字节 (0xAA)- 源地址: 发送方ID (1,2,3)- 目标地址: 接收方ID (1,2,3)- 消息ID: 标识消息类型 (0x01-命令, 0x02-数据, 0x03-应答)- 长度: 数据字段长度- 数据: 消息内容- CRC: 校验字节

2号单片机通讯框架

下面是2号单片机的通讯框架实现,使用状态机处理消息接收,使用双缓冲区实现消息转发:

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>// 定义系统常量
#define SOF             0xAA
#define DEVICE_ID_1     0x01
#define DEVICE_ID_2     0x02
#define DEVICE_ID_3     0x03// 消息类型定义
#define MSG_CMD         0x01
#define MSG_DATA        0x02
#define MSG_ACK         0x03// 消息缓冲区大小
#define MAX_MSG_LENGTH  64
#define BUFFER_SIZE     5// 消息结构体
typedef struct {uint8_t src_addr;uint8_t dst_addr;uint8_t msg_id;uint8_t length;uint8_t data[MAX_MSG_LENGTH];uint8_t crc;
} Message;// 接收状态机枚举
typedef enum {RX_STATE_IDLE,RX_STATE_SOF,RX_STATE_SRC,RX_STATE_DST,RX_STATE_MSGID,RX_STATE_LENGTH,RX_STATE_DATA,RX_STATE_CRC
} RxState;// 全局变量
static Message rx_buffer[BUFFER_SIZE];
static Message tx_buffer[BUFFER_SIZE];
static uint8_t rx_head = 0;
static uint8_t rx_tail = 0;
static uint8_t tx_head = 0;
static uint8_t tx_tail = 0;
static RxState rx_state = RX_STATE_IDLE;
static uint8_t rx_index = 0;
static uint8_t crc_calculated = 0;// 串口初始化函数
void UART1_Init(void) {// 配置1号串口(与1号单片机通信)// 波特率、中断等配置
}void UART2_Init(void) {// 配置2号串口(与3号单片机通信)// 波特率、中断等配置
}// CRC校验计算
uint8_t calculate_crc(uint8_t *data, uint8_t length) {uint8_t crc = 0;for (uint8_t i = 0; i < length; i++) {crc ^= data[i];}return crc;
}// 发送消息函数
void send_message(Message *msg, uint8_t uart_id) {uint8_t buffer[MAX_MSG_LENGTH + 6];uint8_t index = 0;buffer[index++] = SOF;buffer[index++] = msg->src_addr;buffer[index++] = msg->dst_addr;buffer[index++] = msg->msg_id;buffer[index++] = msg->length;for (uint8_t i = 0; i < msg->length; i++) {buffer[index++] = msg->data[i];}buffer[index++] = calculate_crc(buffer, index);// 根据uart_id选择发送串口if (uart_id == 1) {// 通过UART1发送buffer} else {// 通过UART2发送buffer}
}// 1号串口接收中断处理函数
void UART1_RX_IRQHandler(void) {uint8_t data = UART1_ReadByte();switch (rx_state) {case RX_STATE_IDLE:if (data == SOF) {rx_state = RX_STATE_SOF;crc_calculated = data;}break;case RX_STATE_SOF:rx_buffer[rx_head].src_addr = data;rx_state = RX_STATE_SRC;crc_calculated ^= data;break;case RX_STATE_SRC:rx_buffer[rx_head].dst_addr = data;rx_state = RX_STATE_DST;crc_calculated ^= data;break;case RX_STATE_DST:rx_buffer[rx_head].msg_id = data;rx_state = RX_STATE_MSGID;crc_calculated ^= data;break;case RX_STATE_MSGID:rx_buffer[rx_head].length = data;rx_state = RX_STATE_LENGTH;crc_calculated ^= data;rx_index = 0;break;case RX_STATE_LENGTH:rx_buffer[rx_head].data[rx_index++] = data;crc_calculated ^= data;if (rx_index >= rx_buffer[rx_head].length) {rx_state = RX_STATE_CRC;}break;case RX_STATE_CRC:if (data == crc_calculated) {// 消息接收成功,加入接收缓冲区rx_head = (rx_head + 1) % BUFFER_SIZE;}rx_state = RX_STATE_IDLE;break;}
}// 3号串口接收中断处理函数(与UART1_RX_IRQHandler类似)
void UART2_RX_IRQHandler(void) {// 实现类似UART1_RX_IRQHandler的逻辑// 将接收到的消息存入rx_buffer
}// 主循环中的消息处理函数
void process_messages(void) {Message msg;// 处理从1号单片机接收的消息while (rx_tail != rx_head) {msg = rx_buffer[rx_tail];// 如果消息是给3号的,转发到3号if (msg.dst_addr == DEVICE_ID_3) {// 修改源地址为2号msg.src_addr = DEVICE_ID_2;send_message(&msg, 2);  // 通过UART2发送到3号}// 如果消息是给1号的(来自3号的应答),转发到1号else if (msg.dst_addr == DEVICE_ID_1) {// 修改源地址为2号msg.src_addr = DEVICE_ID_2;send_message(&msg, 1);  // 通过UART1发送到1号}rx_tail = (rx_tail + 1) % BUFFER_SIZE;}
}// 主函数
int main(void) {UART1_Init();UART2_Init();// 启用中断UART1_EnableIRQ();UART2_EnableIRQ();while (1) {// 处理消息转发process_messages();// 其他任务// ...}
}

框架优化建议

  1. 优先级机制:为紧急消息设置高优先级,优先处理和转发。

  2. 流量控制:当缓冲区满时,发送忙信号给发送方,防止消息丢失。

  3. 超时重传:对重要消息实现超时重传机制,确保消息可靠传输。

  4. 状态监控:添加通信状态监控,统计消息收发成功率,便于故障排查。

  5. 中断处理优化:尽量减少中断处理时间,关键数据复制到缓冲区后尽快退出中断。

通过以上设计,2号单片机可以高效地实现1号和3号之间的消息中继,同时保证通信的实时性和可靠性。

三、2号单片机的通讯框架如何处理消息丢失和重传?

消息丢失与重传机制设计

为了处理消息丢失和实现可靠重传,2号单片机的通讯框架需要增加确认机制、超时处理和消息缓存功能。以下是具体实现方案:

改进的通信协议

在原有协议基础上增加序列号和确认应答:

消息格式: [SOF][源地址][目标地址][消息ID][序列号][长度][数据][CRC]- 序列号: 8位递增数字(0-255),用于标识消息- 确认应答(ACK)格式: [SOF][源地址][目标地址][MSG_ACK][序列号][0][CRC]

重传机制实现

以下是改进后的2号单片机通讯框架代码,增加了消息缓存和重传机制:

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>// 定义系统常量
#define SOF             0xAA
#define DEVICE_ID_1     0x01
#define DEVICE_ID_2     0x02
#define DEVICE_ID_3     0x03// 消息类型定义
#define MSG_CMD         0x01
#define MSG_DATA        0x02
#define MSG_ACK         0x03// 消息缓冲区大小
#define MAX_MSG_LENGTH  64
#define TX_BUFFER_SIZE  10
#define RX_BUFFER_SIZE  10
#define MAX_RETRIES     3
#define RETRY_TIMEOUT   100  // 毫秒// 消息结构体
typedef struct {uint8_t src_addr;uint8_t dst_addr;uint8_t msg_id;uint8_t seq_num;uint8_t length;uint8_t data[MAX_MSG_LENGTH];uint8_t crc;uint32_t timestamp;  // 发送时间戳uint8_t retries;     // 重试次数bool awaiting_ack;   // 是否等待确认
} Message;// 接收状态机枚举
typedef enum {RX_STATE_IDLE,RX_STATE_SOF,RX_STATE_SRC,RX_STATE_DST,RX_STATE_MSGID,RX_STATE_SEQ,RX_STATE_LENGTH,RX_STATE_DATA,RX_STATE_CRC
} RxState;// 全局变量
static Message tx_buffer[TX_BUFFER_SIZE];  // 发送缓冲区(用于重传)
static Message rx_buffer[RX_BUFFER_SIZE];  // 接收缓冲区
static uint8_t tx_head = 0;
static uint8_t tx_tail = 0;
static uint8_t rx_head = 0;
static uint8_t rx_tail = 0;
static RxState rx_state = RX_STATE_IDLE;
static uint8_t rx_index = 0;
static uint8_t crc_calculated = 0;
static uint8_t next_seq_num = 0;  // 下一个发送的序列号// 串口初始化函数
void UART1_Init(void) {// 配置1号串口(与1号单片机通信)// 波特率、中断等配置
}void UART2_Init(void) {// 配置2号串口(与3号单片机通信)// 波特率、中断等配置
}// CRC校验计算
uint8_t calculate_crc(uint8_t *data, uint8_t length) {uint8_t crc = 0;for (uint8_t i = 0; i < length; i++) {crc ^= data[i];}return crc;
}// 发送消息函数
void send_message(Message *msg, uint8_t uart_id, bool store_for_retry) {uint8_t buffer[MAX_MSG_LENGTH + 7];uint8_t index = 0;buffer[index++] = SOF;buffer[index++] = msg->src_addr;buffer[index++] = msg->dst_addr;buffer[index++] = msg->msg_id;buffer[index++] = msg->seq_num;buffer[index++] = msg->length;for (uint8_t i = 0; i < msg->length; i++) {buffer[index++] = msg->data[i];}buffer[index++] = calculate_crc(buffer, index);// 根据uart_id选择发送串口if (uart_id == 1) {// 通过UART1发送buffer} else {// 通过UART2发送buffer}// 如果需要存储用于重传if (store_for_retry && msg->msg_id != MSG_ACK) {// 复制消息到发送缓冲区memcpy(&tx_buffer[tx_head], msg, sizeof(Message));tx_buffer[tx_head].timestamp = get_current_time();  // 获取当前时间tx_buffer[tx_head].retries = 0;tx_buffer[tx_head].awaiting_ack = true;tx_head = (tx_head + 1) % TX_BUFFER_SIZE;}
}// 1号串口接收中断处理函数
void UART1_RX_IRQHandler(void) {uint8_t data = UART1_ReadByte();switch (rx_state) {case RX_STATE_IDLE:if (data == SOF) {rx_state = RX_STATE_SOF;crc_calculated = data;}break;// 其他状态处理...(与之前类似)case RX_STATE_CRC:if (data == crc_calculated) {// 消息接收成功,加入接收缓冲区if (rx_buffer[rx_head].msg_id == MSG_ACK) {// 处理确认应答,标记对应消息已确认process_ack(&rx_buffer[rx_head]);} else {// 普通消息,转发并发送本地ACKforward_message(&rx_buffer[rx_head]);send_ack(rx_buffer[rx_head].src_addr, rx_buffer[rx_head].seq_num);}rx_head = (rx_head + 1) % RX_BUFFER_SIZE;}rx_state = RX_STATE_IDLE;break;}
}// 3号串口接收中断处理函数
void UART2_RX_IRQHandler(void) {// 与UART1_RX_IRQHandler类似,处理来自3号的消息
}// 处理确认应答
void process_ack(Message *ack) {uint8_t index = tx_tail;while (index != tx_head) {if (tx_buffer[index].awaiting_ack && tx_buffer[index].seq_num == ack->seq_num &&tx_buffer[index].dst_addr == ack->src_addr) {// 标记消息已确认tx_buffer[index].awaiting_ack = false;break;}index = (index + 1) % TX_BUFFER_SIZE;}
}// 转发消息
void forward_message(Message *msg) {Message forward_msg;memcpy(&forward_msg, msg, sizeof(Message));if (msg->dst_addr == DEVICE_ID_3) {// 转发到3号forward_msg.src_addr = DEVICE_ID_2;send_message(&forward_msg, 2, true);} else if (msg->dst_addr == DEVICE_ID_1) {// 转发到1号forward_msg.src_addr = DEVICE_ID_2;send_message(&forward_msg, 1, true);}
}// 发送确认应答
void send_ack(uint8_t dst_addr, uint8_t seq_num) {Message ack_msg;ack_msg.src_addr = DEVICE_ID_2;ack_msg.dst_addr = dst_addr;ack_msg.msg_id = MSG_ACK;ack_msg.seq_num = seq_num;ack_msg.length = 0;if (dst_addr == DEVICE_ID_1) {send_message(&ack_msg, 1, false);} else {send_message(&ack_msg, 2, false);}
}// 检查并重传超时消息
void check_and_retransmit(void) {uint32_t current_time = get_current_time();uint8_t index = tx_tail;while (index != tx_head) {if (tx_buffer[index].awaiting_ack && (current_time - tx_buffer[index].timestamp > RETRY_TIMEOUT)) {// 检查重试次数if (tx_buffer[index].retries >= MAX_RETRIES) {// 达到最大重试次数,处理失败handle_transmission_failure(&tx_buffer[index]);tx_buffer[index].awaiting_ack = false;} else {// 重传消息tx_buffer[index].retries++;tx_buffer[index].timestamp = current_time;if (tx_buffer[index].dst_addr == DEVICE_ID_1) {send_message(&tx_buffer[index], 1, false);} else {send_message(&tx_buffer[index], 2, false);}}}index = (index + 1) % TX_BUFFER_SIZE;}// 清理已确认的消息while (tx_tail != tx_head && !tx_buffer[tx_tail].awaiting_ack) {tx_tail = (tx_tail + 1) % TX_BUFFER_SIZE;}
}// 处理传输失败
void handle_transmission_failure(Message *msg) {// 可以实现错误日志记录或通知上层应用
}// 获取当前时间(伪代码,需根据实际硬件实现)
uint32_t get_current_time(void) {// 返回系统时间(毫秒)return 0;
}// 主循环中的消息处理函数
void process_messages(void) {// 检查并重传超时消息check_and_retransmit();// 处理接收缓冲区中的消息while (rx_tail != rx_head) {// 处理消息...rx_tail = (rx_tail + 1) % RX_BUFFER_SIZE;}
}// 主函数
int main(void) {UART1_Init();UART2_Init();// 启用中断UART1_EnableIRQ();UART2_EnableIRQ();while (1) {// 处理消息转发和重传process_messages();// 其他任务// ...}
}

关键机制说明

  1. 序列号管理

    • 每个消息分配唯一序列号(0-255循环)
    • 用于识别重复消息和匹配ACK应答
  2. 确认应答机制

    • 接收方收到消息后发送ACK应答
    • ACK包含原始消息的序列号
    • 发送方维护待确认消息列表
  3. 超时重传

    • 设置合理的超时时间(RETRY_TIMEOUT)
    • 超过时间未收到ACK则重传
    • 限制最大重试次数(MAX_RETRIES)
  4. 消息缓存

    • 使用环形缓冲区存储待确认消息
    • 消息包含发送时间戳和重试次数

优化建议

  1. 滑动窗口协议

    • 扩展当前实现,支持多消息并发发送
    • 增加窗口大小参数,提高吞吐量
  2. 动态超时调整

    • 根据网络状况动态调整超时时间
    • 实现往返时间(RTT)测量
  3. 流量控制

    • 当发送缓冲区满时拒绝接收新消息
    • 实现基于窗口的流量控制机制
  4. 错误恢复

    • 实现序列号回滚机制
    • 处理序列号溢出情况

通过以上机制,2号单片机可以有效处理消息丢失问题,确保1号和3号之间的通信可靠性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/87219.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/87219.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在服务区已有预装镜像的情况下管理自己的包

你的需求非常明确&#xff1a;希望利用 NGC 镜像预装的主环境包&#xff08;如 PyTorch、CUDA&#xff09;&#xff0c;同时能独立管理自己额外安装的包&#xff0c;避免直接污染主环境。以下是几种解决方案&#xff0c;按推荐度排序&#xff1a; 方案 1&#xff1a;虚拟环境复…

JavaWeb之Servlet(2)RequestResponse..

文章目录 1 Request和Response的概述2 Request对象2.1 Request继承体系2.2 Request获取请求数据2.2.1 获取请求行数据2.2.2 获取请求头数据2.2.3 获取请求体数据1-3小结2.2.4 获取请求参数的通用方式请求参数和请求数据的区别问题案例分析问题解决 2.3 IDEA快速创建Servlet2.4 …

将 h264+g711a存为 mp4文件,记录

将 h264g711a存为 mp4文件&#xff0c;记录 &#x1f4cc; 关键问题&#xff1a;MP4 不原生支持 G.711A MP4 容器格式 不原生支持 G.711&#xff08;包括 A-law&#xff0c;也就是 G.711A&#xff09;音频&#xff0c;所以不能直接将 G.711A 音频封装进 MP4 文件中。常见的做法…

【Elasticsearch】全文检索 组合检索

全文检索 1.全文检索1.1 准备测试数据1.2 案例分析1.2.1 match&#xff08;分词检索&#xff09;1.2.2 match_phrase&#xff08;短语检索&#xff09;1.2.3 match_phrase_prefix&#xff08;短语前缀匹配&#xff09;1.2.4 multi_match&#xff08;多字段匹配&#xff09;1.2.…

信号处理学习——文献精读与code复现之TFN——嵌入时频变换的可解释神经网络(上)

​​​​​​​​​​​​​​TFN: An interpretable neural network with time-frequency transform embedded for intelligent fault diagnosis - ScienceDirecthttps://www.sciencedirect.com/science/article/abs/pii/S0888327023008609?via%3Dihub &#xff08;看看玲娜贝…

Panda3D实战:从入门到精通

Panda3D基础实例 创建一个简单的Panda3D场景,加载一个模型并显示: from direct.showbase.ShowBase import ShowBaseclass MyApp(ShowBase):def __init__(self):ShowBase.__init__(self)self.scene = self.loader.loadModel("models/environment")self.scene.repa…

Galera集群:高可用MySQL同步复制方案

目录 Galera Cluster 概述 核心架构与组件 WSREP API Group Communication System (GCP) 同步复制机制 复制流程详解 冲突检测算法 关键特性 多主架构实现 强一致性保障 自动成员管理 性能优化策略 并行复制实现 流控机制详解 批处理与压缩 部署与监控 详细配…

MybatisPlus-03.快速入门-常用注解

一.MP的原理 mp究竟是如何知道我们需要对哪个表进行操作&#xff0c;并且又是如何知道要操作哪些字段的呢&#xff1f;这是因为mp使用到了反射机制&#xff0c;我们在定义mapper接口时使其继承了BaseMapper接口&#xff0c;并指定了BaseMapper接口泛型为User&#xff0c;因此m…

ABAP+记录一个BDC的BUG修改过程

问题背景&#xff1a; 业务顾问反馈在使用BDC 进行MEQ1进行供应商配额时&#xff0c;由于以前录屏时用例只有3行数据&#xff0c;导致现在有5行数据的时候&#xff0c;代码仍然只获取了3行数据进行录入&#xff0c;现在需要更改代码&#xff0c;使其按照实际情况自动调整行数。…

github上传代码步骤(http)

github上传步骤&#xff08;http&#xff09; 之前github上传不了代码&#xff0c;总是报错。后面发现自己用的ssh上传需要秘钥&#xff0c;现在我介绍一个最简单的http上传方法&#xff08;虽然没有ssh安全。。。但简单嘛~&#xff09;&#xff0c;现在我做个例子&#xff0c…

深入理解Nginx-以实际http通信例子改造带ssl配Nginx的实战-优雅草卓伊凡|麻子

深入理解Nginx-以实际http通信例子改造带ssl配Nginx的实战-优雅草卓伊凡|麻子 SSL/TLS在Nginx中的底层实现原理 Nginx的SSL模块架构 Nginx通过ngx_http_ssl_module模块实现SSL/TLS功能&#xff0c;该模块基于OpenSSL库构建。根据Nginx官方文档&#xff0c;SSL模块在Nginx架构…

AT6558R-5N32介绍

作为单芯片SOC方案&#xff0c;AT6558R在片上整合了射频前端、数字基带处理器与32位RISC CPU&#xff0c;并具备电源管理能力。该芯片兼容北斗、GPS、GLONASS三大卫星导航系统&#xff0c;可实现多模协同定位‌。 主要特征 支持 BDS/GPS/GLONASS 多系统联合定位 和单系统独立定…

“对象创建”模式之原型模式

目录 Prototype 原型模式动机 Motivation引例模式定义结构 Structure要点总结 Prototype 原型模式 动机 Motivation 在软件系统中&#xff0c;经常面临着“某些结构复杂的对象”的创建工作&#xff1b;由于需求的变化&#xff0c;这些对象经常面临着剧烈的变化&#xff0c;但…

Tomcat服务概述

前言&#xff1a; 作为Apache软件基金会Jakarta项目的核心成果&#xff0c;Tomcat凭借其轻量级、开源免费的特性&#xff0c;已成为Java Web应用服务的行业基准。它实现了完整的Servlet与JSP规范&#xff0c;通过模块化架构&#xff08;Connector请求处理层与Container业务逻辑…

HarmonyOS应用开发高级认证知识点梳理 (一) 布局与样式

以下是 HarmonyOS 应用开发中 ‌布局与样式‌ 的核心知识点梳理&#xff08;针对高级认证备考&#xff09;&#xff0c;结合官方文档与高频考点&#xff1a; 一、布局系统核心知识点 布局容器类型‌ 线性布局‌&#xff1a;Column&#xff08;纵向&#xff09;、Row&#xf…

【Ragflow】30.离线环境迁移方案

前言 之前的 Ragflow-Plus 在服务器上稳定运行一段时间后&#xff0c;接到任务&#xff0c;要把服务迁移到一台古老的&#xff0c;离线的windows台式机上。 起初认为&#xff0c;下载离线安装包&#xff0c;加载离线镜像&#xff0c;迁移下数据就可以了。 结果坑多得意想不到…

nrf52840蓝牙学习(定时器的应用)

和其他 MCU 处理器一样&#xff0c;在 nrf52840 中定时器的功能是十分强大的。其内部包含了 5 个定时 器 TIMER 模块 :TIMER0 、 TIMER1 、 TIMER2 、 TIMER3 、 TIMER4 &#xff0c;如下表 10.1 所示。 1. 时钟源 首先定时器 TIMER 工作在高频时钟源&#xff08…

【Bluedroid】蓝牙启动之BTM_reset_complete源码解析

当蓝牙控制器完成硬件重置后,协议栈需通过一系列初始化操作恢复各模块状态。本文深入分析BTM_reset_complete核心函数及其调用链,详解 L2CAP 连接清理、安全模块重置、扫描参数恢复、BLE 隐私功能初始化等关键流程,揭示蓝牙设备在重置后如何通过标准化状态恢复确保互操作性、…

containerd 项目主要目录简要说明

containerd 项目结构清晰&#xff0c;核心代码分布在若干主目录下。以下是 client、cmd、core、internal、pkg、plugins 这六个主要包/目录的简要作用说明&#xff1a; 1. client 作用&#xff1a;封装与 containerd 守护进程通信的 Go 客户端 API&#xff0c;主要基于 gRPC。…

有线转无线工具,轻松创建WiFi热点

软件介绍 今天为大家推荐一款实用的无线网络共享工具——MyPublicWiFi。这款软件能够将电脑的有线网络转换为无线WiFi&#xff0c;方便其他设备连接使用。 安装与设置 该软件为安装版程序&#xff0c;安装完成后会自动识别当前电脑的IP地址。用户可在软件界面中自定义设…