Spring Boot + Redis Sentinel (一主兩從)測試案例

🚀 Spring Boot + Redis Sentinel 完整測試案例

🏷? 標簽:Redis 、Redis Sentinel、Spring Boot 實戰


📚 目錄導航

  1. 📝 前言
  2. 🏗? Redis Sentinel 架構說明
  3. 📦 Docker Compose 搭建 Redis 哨兵環境
  4. ?? Spring Boot 配置
    • 📌 Maven 依賴
    • 📝 application.yml 配置
    • 🔧 Redis 配置類
  5. 🧪 測試 Controller
  6. 🚀 運行測試
  7. ? 為什么這樣配置
  8. 🏁 總結

📝 一、前言

在生產環境中,Redis 通常部署為 一主多從 + 哨兵(Sentinel) 架構,以保證高可用性和數據安全性。
使用 Spring Boot 連接 Redis 哨兵時,開發者可能會遇到以下問題:

  • 哨兵返回主節點名稱(如 redis-master)無法被客戶端解析
  • 數據序列化和反序列化不一致導致 StreamCorruptedException

本文演示如何通過 Docker Compose 搭建 Redis 哨兵環境,并使用 Spring Boot 完成數據寫入和讀取操作。


🏗? 二、Redis Sentinel 架構說明

1. ASCII 拓撲示意

          ┌─────────────┐│ redis-master││    6379     │└─────┬───────┘│┌─────────┴─────────┐│                   │
┌─────────────┐     ┌─────────────┐
│ redis-slave1│     │ redis-slave2│
│    6380     │     │    6381     │
└─────────────┘     └─────────────┘▲                   ▲│                   │
┌─────┴─────┐       ┌─────┴─────┐
│ sentinel1 │       │ sentinel2 │
│  26379    │       │  26380    │
└───────────┘       └───────────┘▲│┌───────────┐│ sentinel3 ││  26381    │└───────────┘

2. Mermaid 彩色架構圖

redis-master:6379
redis-slave1:6380
redis-slave2:6381
sentinel1:26379
sentinel2:26380
sentinel3:26381

🔹 主節點(紅色)、從節點(綠色)、哨兵(藍色),箭頭表示數據同步和監控方向。


📦 三、Docker Compose 搭建 Redis 哨兵環境

Docker Compose 搭建Redis哨兵


?? 四、Spring Boot 配置

📌 1. Maven 依賴

    <dependencies><!-- Web 模塊 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Spring Data Redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- Jackson --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

📝 2. application.yml 配置

spring:data:redis:sentinel:nodes:- 192.168.3.150:26379- 192.168.3.150:26380- 192.168.3.150:26381master: mymastertimeout: 3000mslettuce:shutdown-timeout: 100mspool:max-active: 8max-idle: 8min-idle: 0max-wait: -1
logging:level:io.lettuce.core: DEBUGorg.springframework.data.redis: DEBUG

🔧 3. Redis 配置類

package com.example.demo.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);template.setKeySerializer(template.getStringSerializer());template.setHashKeySerializer(template.getStringSerializer());template.afterPropertiesSet();return template;}@Beanpublic StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {return new StringRedisTemplate(factory);}
}

使用 StringRedisTemplate 避免 Java 默認序列化問題。


🧪 五、測試 Controller

package com.example.demo.controller;import com.example.demo.service.RedisService;
import org.springframework.web.bind.annotation.*;import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;@RestController
@RequestMapping("/redis")
public class RedisController {private final RedisService redisService;// 構造函數注入,Spring 會自動注入 RedisService Beanpublic RedisController(RedisService redisService) {this.redisService = redisService;}// String 操作示例@PostMapping("/string/set")public String setString(@RequestParam String key, @RequestParam String value) {redisService.set(key, value, 60L, TimeUnit.SECONDS);return "String set successfully";}// String獲取Key@GetMapping("/string/get")public Object getString(@RequestParam String key) {return redisService.get(key);}
}

🚀 六、運行測試

1. 啟動 Docker Compose:

docker compose up -d

2. 啟動 Spring Boot 應用

3. 測試寫入:

curl "http://localhost:9090/redis/set?key=test&value=HelloRedis"

在這里插入圖片描述

4. 測試讀取:

curl "http://localhost:9090/redis/get?key=test"

在這里插入圖片描述

5. 驗證主從同步:

docker exec -it redis-slave1 redis-cli GET test
docker exec -it redis-slave2 redis-cli GET test

在這里插入圖片描述

數據應在主從節點一致。

6.可能出現的問題:

2025-08-14T15:51:45.540+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] i.lettuce.core.protocol.DefaultEndpoint  : [channel=0xec254466, /192.168.3.36:49466 -> /192.168.3.150:26379, epid=0x2] closeAsync()
2025-08-14T15:51:45.543+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] io.lettuce.core.RedisClient              : Resolved SocketAddress redis-master/<unresolved>:6379 using redis-sentinel://192.168.3.150,192.168.3.150:26380,192.168.3.150:26381?sentinelMasterId=mymaster&timeout=3s
2025-08-14T15:51:45.543+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] io.lettuce.core.AbstractRedisClient      : Connecting to Redis at redis-master/<unresolved>:6379
2025-08-14T15:51:45.545+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] i.lettuce.core.protocol.CommandHandler   : [channel=0xec254466, /192.168.3.36:49466 -> /192.168.3.150:26379, epid=0x2, chid=0x1] channelInactive()
2025-08-14T15:51:45.546+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] i.lettuce.core.protocol.DefaultEndpoint  : [channel=0xec254466, /192.168.3.36:49466 -> /192.168.3.150:26379, epid=0x2] deactivating endpoint handler
2025-08-14T15:51:45.546+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] i.lettuce.core.protocol.CommandHandler   : [channel=0xec254466, /192.168.3.36:49466 -> /192.168.3.150:26379, epid=0x2, chid=0x1] channelInactive() done
2025-08-14T15:51:45.547+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] i.l.core.protocol.ConnectionWatchdog     : [channel=0xec254466, /192.168.3.36:49466 -> /192.168.3.150:26379, last known addr=/192.168.3.150:26379] channelInactive()
2025-08-14T15:51:45.547+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] i.l.core.protocol.ConnectionWatchdog     : [channel=0xec254466, /192.168.3.36:49466 -> /192.168.3.150:26379, last known addr=/192.168.3.150:26379] Reconnect scheduling disabled
2025-08-14T15:51:45.547+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] i.lettuce.core.protocol.CommandHandler   : [channel=0xec254466, /192.168.3.36:49466 -> /192.168.3.150:26379, epid=0x2, chid=0x1] channelUnregistered()
2025-08-14T15:51:47.799+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-2] io.lettuce.core.AbstractRedisClient      : Connecting to Redis at redis-master/<unresolved>:6379: {}java.net.UnknownHostException: 不知道這樣的主機。 (redis-master)at java.base/java.net.Inet6AddressImpl.lookupAllHostAddr(Native Method) ~[na:na]at java.base/java.net.InetAddress$PlatformNameService.lookupAllHostAddr(InetAddress.java:933) ~[na:na]at java.base/java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1543) ~[na:na]at java.base/java.net.InetAddress$NameServiceAddresses.get(InetAddress.java:852) ~[na:na]at java.base/java.net.InetAddress.getAllByName0(InetAddress.java:1532) ~[na:na]at java.base/java.net.InetAddress.getAllByName(InetAddress.java:1384) ~[na:na]at java.base/java.net.InetAddress.getAllByName(InetAddress.java:1305) ~[na:na]at java.base/java.net.InetAddress.getByName(InetAddress.java:1255) ~[na:na]at io.netty.util.internal.SocketUtils$8.run(SocketUtils.java:156) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.internal.SocketUtils$8.run(SocketUtils.java:153) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at java.base/java.security.AccessController.doPrivileged(AccessController.java:569) ~[na:na]at io.netty.util.internal.SocketUtils.addressByName(SocketUtils.java:153) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.resolver.DefaultNameResolver.doResolve(DefaultNameResolver.java:41) ~[netty-resolver-4.1.123.Final.jar:4.1.123.Final]at io.netty.resolver.SimpleNameResolver.resolve(SimpleNameResolver.java:61) ~[netty-resolver-4.1.123.Final.jar:4.1.123.Final]at io.netty.resolver.SimpleNameResolver.resolve(SimpleNameResolver.java:53) ~[netty-resolver-4.1.123.Final.jar:4.1.123.Final]at io.netty.resolver.InetSocketAddressResolver.doResolve(InetSocketAddressResolver.java:55) ~[netty-resolver-4.1.123.Final.jar:4.1.123.Final]at io.netty.resolver.InetSocketAddressResolver.doResolve(InetSocketAddressResolver.java:31) ~[netty-resolver-4.1.123.Final.jar:4.1.123.Final]at io.netty.resolver.AbstractAddressResolver.resolve(AbstractAddressResolver.java:106) ~[netty-resolver-4.1.123.Final.jar:4.1.123.Final]at io.netty.bootstrap.Bootstrap.doResolveAndConnect0(Bootstrap.java:220) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.bootstrap.Bootstrap.access$000(Bootstrap.java:47) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.bootstrap.Bootstrap$1.operationComplete(Bootstrap.java:189) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.bootstrap.Bootstrap$1.operationComplete(Bootstrap.java:175) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:603) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:570) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:505) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:649) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:638) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:118) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.channel.DefaultChannelPromise.trySuccess(DefaultChannelPromise.java:84) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.channel.AbstractChannel$AbstractUnsafe.safeSetSuccess(AbstractChannel.java:988) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.channel.AbstractChannel$AbstractUnsafe.register0(AbstractChannel.java:515) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.channel.AbstractChannel$AbstractUnsafe.access$200(AbstractChannel.java:428) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.channel.AbstractChannel$AbstractUnsafe$1.run(AbstractChannel.java:485) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:173) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:166) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:569) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at java.base/java.lang.Thread.run(Thread.java:842) ~[na:na]
1. 為什么會這樣

Docker 內部可以通過容器名 redis-master 互相訪問(因為有自定義網絡和 DNS)。

但是你的 Spring Boot 是在宿主機運行(不是在 Docker 內部),宿主機默認并不認識 redis-master 這個名字。

哨兵返回的主節點地址是它內部配置的 redis-master(來自 sentinel.conf 或 docker-compose 服務名),但宿主機解析不了。

2. 解決方案

在SpringBoot 主機 C:\Windows\System32\drivers\etc 加映射

如果 redis-master 容器的 IP 是 192.168.3.150(或者你用的是橋接 IP):

在這里插入圖片描述
這樣宿主機就能解析 redis-master 了。


? 七、為什么這樣配置

  1. 哨兵模式:自動故障轉移,保證高可用
  2. announce-ip 配置 IP:避免容器名解析問題,防止 UnknownHostException
  3. StringRedisTemplate:避免序列化異常,方便開發調試
  4. Docker Compose:快速搭建一主兩從 + 三哨兵環境,便于測試

🏁 八、總結

  • Redis Sentinel + Spring Boot 可以輕松實現高可用讀寫

  • 注意:

    • 哨兵返回 IP 避免主機名解析問題
    • 數據序列化需與存儲類型匹配
  • 本方案適合開發、測試和小型生產環境

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/918798.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/918798.shtml
英文地址,請注明出處:http://en.pswp.cn/news/918798.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

力扣-295.數據流的中位數

題目鏈接 295.數據流的中位數 class MedianFinder {PriorityQueue<Integer> left;//隊頭最大PriorityQueue<Integer> right;//隊頭最小public MedianFinder() {left new PriorityQueue<>(new Comparator<Integer>() {Overridepublic int compare(In…

【數據分享】2014-2023年長江流域 (0.05度)5.5km分辨率的每小時日光誘導葉綠素熒光SIF數據

而今天要說明數據就是2014-2023年長江流域 &#xff08;0.05度&#xff09;5.5km分辨率的每小時日光誘導葉綠素熒光SIF數據。數據介紹一、數據集概況&#xff1a;長江流域植被動態的 “每小時快照”本文分享的核心數據集為2014 年 9 月至 2023 年 9 月長江流域日光誘導葉綠素熒…

計算機二級 Web —— HTML 全面精講(含真題實戰)

例題來源: web.code2ji.cn 0. HTML 基礎與全局常識 0.1 HTML 是什么 HTML&#xff08;HyperText Markup Language&#xff09;是網頁結構語言&#xff0c;用“標簽”描述內容、層次與含義。 0.2 基本文檔骨架&#xff08;必須熟練&#xff09; <!DOCTYPE html> <…

Linux中的日志管理

注&#xff1a;在 centos7/Rocky9 中&#xff0c;系統日志消息由兩個服務負責處理&#xff1a;systemd-journald 和 rsyslog一、常見日志文件的作用實驗一&#xff1a;測試查看暴力破解系統密碼的IP地址步驟一&#xff1a;故意輸錯密碼3次&#xff0c;在日志文件中查看步驟二&a…

C++ 性能優化擂臺:挑戰與突破之路

一、引言&#xff08;一&#xff09;C 在性能關鍵領域的地位在當今數字化時代&#xff0c;C 語言憑借其高效性、靈活性和對硬件的直接操控能力&#xff0c;在眾多對性能要求極高的領域中占據著舉足輕重的地位。無論是構建高性能的游戲引擎&#xff0c;實現金融領域毫秒級響應的…

五、Elasticsearch在Linux的安裝部署

五、Elasticsearch在Linux的安裝部署 文章目錄五、Elasticsearch在Linux的安裝部署1.Elasticsearch的作用2.安裝0. 安裝前準備1.使用包管理器安裝&#xff08;推薦&#xff0c;自動服務化&#xff09;Ubuntu / DebianRHEL / CentOS / Rocky / Alma2. 使用 tar.gz 安裝&#xff…

Kubernetes集群部署全攻略

目錄 一、 服務器環境及初始化 1、架構分析 2、初始化 2.1、清空Iptales默認規則及關閉防火墻 2.2、關閉SELINUX 2.3、關閉Swap交換空間 2.4、設置主機名 2.5、編寫hosts文件 2.6、設置內核參數 二、安裝Docker環境 1、安裝Docker 1.1、配置阿里源 1.2、安裝docke…

Ceph存儲池詳解

Ceph 存儲池&#xff08;Pool&#xff09;詳解 Ceph 的 存儲池&#xff08;Pool&#xff09; 是邏輯存儲單元&#xff0c;用于管理數據的分布、冗余和訪問策略。它是 Ceph 存儲集群的核心抽象&#xff0c;支持 對象存儲&#xff08;RGW&#xff09;、塊存儲&#xff08;RBD&…

使用 Docker 部署 PostgreSQL

通過 Docker 部署 PostgreSQL 是一種快速、高效的方式&#xff0c;適用于開發和測試環境。 步驟 1&#xff1a;拉取 PostgreSQL 鏡像 運行以下命令從 Docker Hub 拉取最新的 PostgreSQL 鏡像&#xff1a; docker pull postgres 如果需要其他的鏡像&#xff0c;可以指定版本…

P1886 滑動窗口 /【模板】單調隊列【題解】

P1886 滑動窗口 /【模板】單調隊列 題目描述 有一個長為 nnn 的序列 aaa&#xff0c;以及一個大小為 kkk 的窗口。現在這個窗口從左邊開始向右滑動&#xff0c;每次滑動一個單位&#xff0c;求出每次滑動后窗口中的最小值和最大值。 例如&#xff0c;對于序列 [1,3,?1,?3,5,3…

河南萌新聯賽2025第(五)場:信息工程大學補題

文章目錄[TOC](文章目錄)前言A.宇宙終極能量調和與多維時空穩定性驗證下的基礎算術可行性研究B.中位數C.中位數1F.中位數4G.簡單題H.簡單題I.Re:從零開始的近世代數復習&#xff08;easy&#xff09;K.狂飆追擊L.防k題前言 這次萌新聯賽考到了很多數學知識 A.宇宙終極能量調和…

SuperMap GIS基礎產品FAQ集錦(20250804)

一、SuperMap iServer 問題1&#xff1a;iServer的名稱和logo怎么自定義&#xff1f; 11.3.0 【解決辦法】參考&#xff1a;https://blog.csdn.net/supermapsupport/article/details/144744640 問題2&#xff1a;iServer 刷新工作空間&#xff0c;當數據庫是 PostGIS 時&#x…

AWS CloudFormation批量刪除指南:清理Clickstream Analytics堆棧

概述 在AWS環境管理中,經常會遇到需要批量刪除CloudFormation堆棧的情況。本文記錄了一次完整的Clickstream Analytics堆棧清理過程,包括遇到的問題和解決方案,希望能為其他開發者提供參考。 背景 我們的AWS賬戶中部署了多個Clickstream Analytics解決方案的CloudFormati…

redis中分布式鎖的應用

我們之前講了秒殺模塊的實現&#xff0c;使用了sychronized互斥鎖&#xff0c;但是在集群模式下因為不同服務器有不同jvm&#xff0c;所以synchronized互斥鎖失效了。 redis實現秒殺超賣問題的解決方案&#xff1a;(僅限于單體項目)-CSDN博客 這時就要找到一個多臺服務器都能…

【科研繪圖系列】R語言繪制微生物豐度和基因表達值的相關性網絡圖

文章目錄 介紹 加載R包 數據下載 導入數據 數據預處理 畫圖 系統信息 參考 介紹 【科研繪圖系列】R語言繪制微生物豐度和基因表達值的相關性網絡圖 加載R包 library(tidyverse) library(ggsignif) library(RColorBrewer) library(dplyr) library(reshape2) library(grid

Pycharm現有conda環境有對應env,但是添加后沒反應

一、系統環境 二、異常現象 Pycharm現有conda環境有對應env&#xff1a; anaconda3的envs下也確實存在這個環境&#xff1a; 但是添加后沒反應&#xff08;點擊確認后&#xff0c;yolov7環境沒有出現在列表中&#xff09;&#xff1a; 但是我之前在別的機子添加是沒問題的。 …

Git常用指令大全:從入門到精通

Git 的常用指令&#xff0c;分為基礎操作、分支管理、遠程協作、撤銷操作和高級功能五個部分&#xff0c;并附上實用示例&#xff1a;一、基礎操作&#xff08;必會&#xff09;初始化倉庫 git init # 在當前目錄創建新倉庫克隆遠程倉庫 git clone https://github.com/user/rep…

Redis (REmote DIctionary Server) 高性能數據庫

Redis {REmote DIctionary Server} 高性能數據庫1. What is Redis?1.1. 基于內存的數據存儲2. Install Redis on Linux3. Starting and stopping Redis in the background3.1. systemctl3.2. service 4. Connect to Redis5. 退出 Redis 的命令行界面 (redis-cli)6. redis-serv…

MySQL中的DML(二)

DML(Data Manipulation Language) : 數據庫操作語言&#xff0c;對數據庫中表的數據進行增刪改操作。 創建student表&#xff1a; CREATE DATABASE test; use test; CREATE TABLE student (id int,name varchar(255),address varchar(255),city varchar(255) );INSERT INTO stu…

linux 主機驅動(SPI)與外設驅動分離的設計思想

一、 主機驅動與外設驅動分離Linux中的SPI、I2c、USB等子系統都利用了典型的把主機驅動和外設驅動分離的想法&#xff0c;讓主機端負責產生總線上的傳輸波形&#xff0c;而外設端只是通過標準的API來讓主機端以適當的波形訪問自身。因此這里涉及了4個軟件模塊&#xff1…