基于 WebSocket 打造聊天室

一、什么是 WebSocket?

WebSocket 是一种基于TCP连接上进行 全双工 通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在这里插入图片描述

二、WebSocket 的应用场景

WebSocket 的特点,决定了他在一些 实时双向通信低延迟推送等功能的应用场景下的独特优势。

在WebSocket 出现之前,Web 程序想要进行实时通信是非常麻烦的,因为 HTTP 是一种无状态、无连接、单向的应用层协议,它无法实现服务器主动向客户端推送消息,并且这种单向请求的特点,注定了客户端实时获取服务端的变化就非常麻烦,通常情况下,需要客户端频繁的对服务器进行轮询,这种方式不仅效率低,实时性不好,而且这种频繁的请求和响应对服务器来说也是一种负担。

三、WebSocket API

1、客户端/浏览器 API

由于 WebSocket 协议是 HTML5开始支持的,所以想要使用 WebSocket 协议,首先保证浏览器支持 HTML5.

websocket 对象创建

let ws = new WebSocket(URL);

:这里的 url 格式为 协议://ip地址:端口号/访问路径。协议名称固定为 ws

websocket 事件

事件事件处理程序描述
openws.onopen连接建立时触发
messagews.onmessage客户端接收服务端数据时触发
errorws.onerror通信发生错误时触发
closews.onclose连接关闭时触发

websocket 方法

方法描述
ws.send()使用连接发送数据到服务器
ws.close()关闭连接

2、服务端 API

Tomcat 从 7.0.5 版本开始支持 WebSocket,所以使用时注意 Tomcat 版本不能低于 7.0.5。在Java中,Endpoint 表示服务器 Websocket 连接的一端,可以视之为处理WebSocket消息的接口。Java 中可以通过两种方式定义Endpoint:

  1. 编程式:通过继承 javax.websocket.Endpoint并实现其方法。
  2. 注解式:通过添加 @ServerEndpoint 等相关注解。

Endpoint 实例在 WebSocket 握手时创建,并在客户端与服务端连接过程中有效,在连接关闭时结束,在Endpoint接口中就定义了其生命周期的相关方法:

方法注解描述
onOpen@OnOpen注解所标识的方法连接建立时触发
onError@OnError注解所标识的方法通信发生错误时触发
onClose@OnClose注解所标识的方法连接关闭时触发

在 onOpen 方法中,将创建一个 Session 对象,表示当前 WebSocket 连接的会话,Session对象提供了一组方法,用于管理WebSocket连接和事件。例如:

服务端向客户端发送数据

如果使用编程式的话,可以通过 Session 添加 MessageHandler 消息处理器来接收消息;如果采用注解式,可以在定义的 Endpoint 类中添加 @OnMessage 注解指定接收消息的方法。

服务端将数据推送给客户端

在 Session 中维护了一个 RemoteEndpoint 实例,用来向客户端推送数据,我们可以通过 Session.getBasicRemote 获取同步消息发送实例,通过 Session.getAsyncRemote 获取异步消息发送实例,然后调用实例中的 sendXXX() 方法即可发送消息。

四、使用 webSocket 实现在线聊天室

上面说了那么多理论知识,主要是为了让大家对Websocket有个最基本的认识,下面就通过一个具体的场景《在线聊天室》来体会一下 Websocket 在实际中如何使用。

1、需求分析

这里我们实现的聊天室是比较简单的,主要功能就是能够实现实时消息的发送和接收,能够实时显示用户在线状态和在线人数。下面简单演示一下成品效果:

2、实现流程

3、消息格式

这里约定前后端的消息数据格式采用 json 格式进行传输。主要分为两大类消息:

  1. 客户端–>服务器:
{"toName":"张三","message":"你好"}
  1. 服务器–>客户端:
{"isSystem":true,"fromName":null,"message":["张三","李四"] }
{"isSystem":false,"fromName":"张三","message":"你好" }

4、服务端功能实现

这部分我会详细介绍一下使用 websocket,后端如何进行api编写,至于登录功能,可以说是千篇一律,这里直接给出代码,不作为重点,也就不在展开介绍了。

(1)导入依赖
首先我们需要创建 SpringBoot 项目并导入 websocket 相关依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

(2)登录模块实现
登录功能不是本次实现的重点,所以这里只是对密码进行了一个简单的校验,不涉及到数据库交互操作。

@RestController
@RequestMapping("/user")
public class UserController {/*** 登录* @param user 提交的用户名和密码* @param httpSession 提交的* @return 成功信息*/@PostMapping("/login")public Result login(User user, HttpSession httpSession) {if (user != null && (user.getPassword()).equals("123")) {// 将用户数据存储到session中httpSession.setAttribute("user",user.getUsername());System.out.println(httpSession);return Result.success("登录成功");} else {return Result.failed();}}/*** 获取当前登录的用户名* @return 用户名*/@GetMapping("/getusername")public Result login(HttpSession httpSession) {String username = (String) httpSession.getAttribute("user");return Result.success(username);}}

(3)聊天室功能实现

@Component
@ServerEndpoint(value = "/chat",configurator = GetHttpSessionConfigurator.class)
public class ChatEndpoint {// 用来存储每一个客户端对象对应的ChatEndpoint对象private static ConcurrentHashMap<String,ChatEndpoint> onlineClient = new ConcurrentHashMap<>();// 声明session对象,通过该对象可以发生消息给指定的客户端private Session session;// 声明Httpsession对象,里面记录了用户相关信息private HttpSession httpSession;/*** 连接建立时调用* @param session 当前连接的会话* @param config Endpoint的配置对象*/@OnOpenpublic void onOpen(Session session,EndpointConfig config) {try {// 初始化当前Endpoint的链接会话this.session = session;// 初始化httpSessionHttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());this.httpSession = httpSession;// 将当前连接的ChatEndpoint对象存储到onlineClient容器中String username = (String) httpSession.getAttribute("user");onlineClient.put(username,this);// 将在线用户推送给所有客户端// 1.获取消息String message = MessageUtils.getMessage(true, null, getNames());// 2.将消息广播给所有用户broadcastAllUsers(message);} catch (Exception e) {e.printStackTrace();}}private void broadcastAllUsers(String message) {Set<String> names = getNames();try {for (String name : names) {// 获取当前用户的ChatEndpoint对象ChatEndpoint client = onlineClient.get(name);// 使用session发送信息client.session.getBasicRemote().sendText(message);}} catch (Exception e) {e.printStackTrace();}}private Set<String> getNames() {return onlineClient.keySet();}/*** 接收到客户端数据后调用* @param message 消息信息* @param session 当前连接的会话*/@OnMessagepublic void onMessage(String message,Session session) {try {// 导入jackson核心类ObjectMapper objectMapper = new ObjectMapper();// 将JSON字符串序列化为Message对象Message mess = objectMapper.readValue(message, Message.class);// 取出接收人String toName = mess.getToName();// 取出信息String sendMess = mess.getMessage();// 找到发送人姓名String fromName = (String) httpSession.getAttribute("user");// 进行消息构造String finalMess = MessageUtils.getMessage(false, fromName, sendMess);// 根据接收人找到对应的连接ChatEndpoint chatEndpoint = onlineClient.get(toName);if (chatEndpoint == null) {// 这里可以抛出异常// ...// 打印日志System.out.println("找不到对应的客户端!userName = "+toName);return;}// 使用对应的会话Session发送给对应的客户端chatEndpoint.session.getBasicRemote().sendText(finalMess);} catch (Exception e) {e.printStackTrace();}}/*** 连接过程中发生错误时调用*/@OnErrorpublic void onError(Session session, Throwable error) {System.out.println("websocket连接出错,错误原因:"+error.getMessage());}/*** 关闭连接时调用*/@OnClosepublic void onClose(Session session) {try {// 1.清除容器中的该ChatEndpoint对象String username = (String) httpSession.getAttribute("user");onlineClient.remove(username);// 2.向所有客户端广播下线通知Set<String> names = getNames();// 3.构造消息String message = MessageUtils.getMessage(true,null,getNames());// 4.广播给其他用户broadcastAllUsers(message);} catch (Exception e) {e.printStackTrace();}}
}

需要注意的是:

  1. 事实上当@Component注解和@ServerEndpoint(“/chat”)注解同时使用时,Spring并不会将ChatEndpoint这个类注册为服务器的端点,我们还需要需要注册一个 ServerEndpointExporter bean,它会自动扫描带有@ServerEndpoint注解的类,并将其注册为WebSocket端点,以便客户端可以连接和通信:
@Configuration
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}
  1. 在WebSocket中,每次客户端与服务器建立连接时,都会创建一个新的ChatEndpoint实例来处理该连接。因此,每个WebSocket连接都有其自己的ChatEndpoint实例,this指向的是当前连接对应的实例,所以在ChatEndpoint类中使用了一个static类型的集合将所有的连接记录起来,便于之后的消息处理。

  2. 在客户端与服务端建立连接时,上述的静态集合中是以 键-值 方式存储每个客户端的ChatEndpoint实例的,为了区分,这里以登录的用户名作为键,所以需要获取到HttpSession,再从Httpsession中获取到用户名,但是在OnOpen方法中无法直接获取当前登录的 Httpsession,此时我们可以借助OnOpen中的另一个参数 EndpointConfig config,将Httpsession存储到EndpointConfig对象中,在从EndpointConfig对象中获取到Httpsession,因此我们需要进行如下配置:

public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {@Overridepublic void modifyHandshake(ServerEndpointConfig sec,HandshakeRequest request, HandshakeResponse response) {// 获取到httpSessionHttpSession httpSession = (HttpSession) request.getHttpSession();// 将httpSession存储到EndpointConfig中sec.getUserProperties().put(HttpSession.class.getName(),httpSession);}
}

5、客户端交互功能实现

客户端这里主要介绍一下前后端交互的逻辑,具体页面实现我们暂时不关心。
(1)登录页面前后端交互
登录页面的前后端交互主要采用 jQuery 发送 Ajax 请求完成,整体思路很简单,这里给出 js 代码:

<script>function login(){// 1.参数校验var username = jQuery("#username");var password = jQuery("#password");if (username.val().trim() == "") {username.focus();alert("请输入用户名!")return false;}if (password.val().trim() == "") {username.focus();alert("请输入密码!");return false;}// 2.将参数提交给后端jQuery.ajax({url:"/user/login",type:"POST",data:{"username":username.val().trim(),"password":password.val().trim()},success:function(res) {console.log(res);// 3.将结果返回给用户if (res.flag) {// 登录成功!// alert("登录成功!");// 跳转到主页location.href="main.html";} else {// 登录失败!alert("登录失败,用户名或密码错误!");}}});}
</script>

(2)聊天页面的前后端交互
在聊天页面这里,使用到 vue 进行处理,可能理解起来稍微有点复杂,大家可以自行参考:

<body>
<div class="abs cover contaniner" id="app"><div class="abs cover pnl"><div class="top pnl-head" style="padding: 20px ; color: white;"><div id="userName">用户:{{username}}<span style='float: right;color: green' v-if="isOnline">在线</span><span style='float: right;color: red' v-else>离线</span></div><div id="chatMes" v-show="chatMes" style="text-align: center;color: #6fbdf3;font-family: 新宋体">正在和 <font face="楷体">{{toName}}</font> 聊天</div></div><!--聊天区开始--><div class="abs cover pnl-body" id="pnlBody"><div class="abs cover pnl-left" id="initBackground" style="background-color: white; width: 100%"><div class="abs cover pnl-left" id="chatArea" v-show="isShowChat"><div class="abs cover pnl-msgs scroll" id="show"><div class="pnl-list" id="hists"><!-- 历史消息 --></div><div class="pnl-list" id="msgs" v-for="message in historyMessage"><!-- 消息这展示区域 --><div class="msg guest" v-if="message.toName"><div class="msg-right"><div class="msg-host headDefault"></div><div class="msg-ball">{{message.message}}</div></div></div><div class="msg robot" v-else><div class="msg-left" worker=""><div class="msg-host photo"style="background-image: url(img/avatar/Member002.jpg)"></div><div class="msg-ball">{{message.message}}</div></div></div></div></div><div class="abs bottom pnl-text"><div class="abs cover pnl-input"><textarea class="scroll" id="context_text" @keyup.enter="submit" wrap="hard" placeholder="在此输入文字信息..."v-model="sendMessage.message"></textarea><div class="abs atcom-pnl scroll hide" id="atcomPnl"><ul class="atcom" id="atcom"></ul></div></div><div class="abs br pnl-btn" id="submit" @click="submit"style="background-color: rgb(32, 196, 202); color: rgb(255, 255, 255);">发送</div><div class="pnl-support" id="copyright"><a href="https://blog.csdn.net/LEE180501?spm=1000.2115.3001.5343">仅供学习参考</a></div></div></div><!--聊天区 结束--><div class="abs right pnl-right"><div class="slider-container hide"></div><div class="pnl-right-content"><div class="pnl-tabs"><div class="tab-btn active" id="hot-tab">好友列表</div></div><div class="pnl-hot"><ul class="rel-list unselect"><li class="rel-item" v-for="friend in friendsList"><a @click='showChat(friend)'>{{friend}}</a></li></ul></div></div><div class="pnl-right-content"><div class="pnl-tabs"><div class="tab-btn active">系统广播</div></div><div class="pnl-hot"><ul class="rel-list unselect" id="broadcastList"><li class="rel-item" style="color: #9d9d9d;font-family: 宋体" v-for="name in systemMessages">您的好友{{name}} 已上线</li></ul></div></div></div></div></div></div>
</div>
<script src="js/vue.js"></script>
<script src="js/axios-0.18.0.js"></script>
<script>let ws;new Vue({el: "#app",data() {return {isShowChat: false,chatMes: false,isOnline: true,username:"",toName: "",sendMessage: {toName: "",message: ""},inputMessage: "",historyMessage: [],friendsList: [],systemMessages : []}},created() {this.init();},methods: {async init() {await axios.get("user/getusername").then(res => {this.username = res.data.message;});//创建webSocket对象ws = new WebSocket("ws://127.0.0.1:8080/chat");//给ws绑定事件ws.onopen = this.onopen;//接收到服务端推送的消息后触发ws.onmessage = this.onMessage;ws.onclose = this.onClose;},showChat(name) {this.toName = name;//清除聊天区的数据let history = sessionStorage.getItem(this.toName);if (!history) {this.historyMessage = [];} else {this.historyMessage = JSON.parse(history);}//展示聊天对话框this.isShowChat = true;//显示“正在和谁聊天”this.chatMes = true;},submit() {this.sendMessage.toName = this.toName;this.historyMessage.push(JSON.parse(JSON.stringify(this.sendMessage)));sessionStorage.setItem(this.toName, JSON.stringify(this.historyMessage));ws.send(JSON.stringify(this.sendMessage));this.sendMessage.message = "";},onOpen() {this.isOnline = true;},onClose() {sessionStorage.clear();this.isOnline = false;},onMessage(evt) {//获取服务端推送过来的消息var dataStr = evt.data;//将dataStr 转换为json对象var res = JSON.parse(dataStr);//判断是否是系统消息if(res.system) {//系统消息  好友列表展示var names = res.message;this.friendsList = [];this.systemMessages = [];for (let i = 0; i < names.length; i++) {if(names[i] != this.username) {this.friendsList.push(names[i]);this.systemMessages.push(names[i]);}}}else {//非系统消息var history = sessionStorage.getItem(res.fromName);if (res.fromName == this.toName) {if (!history) {this.historyMessage = [res];} else {this.historyMessage.push(res);}sessionStorage.setItem(res.fromName, JSON.stringify(this.historyMessage));} else {if (!history) {sessionStorage.setItem(res.fromName, JSON.stringify([res]));} else {let messages = JSON.parse(history);messages.push(res);sessionStorage.setItem(res.fromName, JSON.stringify(messages));}}}}}});
</script>

6、聊天室完整资源

完整资源请点击: Gitee 在线聊天室

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

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

相关文章

Windows 10如何关闭系统自动更新(实用教程)

本章教程&#xff0c;用最简洁的方式介绍在windows10中如何关闭系统自动更新。 目录 一、关闭自动更新服务 二、关闭自动更新组策略 一、关闭自动更新服务 1、 winr 2、services.msc 3、找到并双击 Windows Update 修改启动类型为禁用 二、关闭自动更新组策略 1、winr 2、gp…

Next.js 学习笔记(一)——安装

安装 系统要求&#xff1a; Node.js 18.17 或更高版本支持 macOS、Windows&#xff08;包括 WSL&#xff09;和 Linux 自动安装 我们建议使用 create-next-app 启动一个新的 Next.js 应用程序&#xff0c;该应用程序会自动为你设置所有内容。要创建项目&#xff0c;请运行&…

3. 内容模块管理 - 异常处理与校验

文章目录 内容模块管理一、自定义异常1.1 全局异常处理器1.2 自定义异常1.3 异常统一响应类1.4 封装通用异常信息 二、JSR303校验2.1 Maven坐标2.2 校验规则2.3 代码示例2.4 捕捉校验异常2.5 分组校验2.6 备注 三、全局异常处理23.1 全局异常处理器3.2 结果集3.3 常用注解3.3.1…

【漏洞复现】捷诚管理信息系统 SQL注入漏洞

漏洞描述 捷诚管理信息系统是一款功能全面,可以支持自营、联营到外柜租赁的管理,其自身带工作流管理工具,能够帮助企业有效的开展内部审批工作。 该系统CWSFinanceCommon.asmx接口存在SQL注入漏洞。未经身份认证的攻击者可以通过该漏洞获取数据库敏感信息,深入利用可获取…

Redis设计与实现之整数集合

目录 一、内存映射数据结构 二、整数集合 1、整数集合的应用 2、数据结构和主要操作 3、intset运行实例 创建新intset 添加新元素到 intset 添加新元素到 intset&#xff08;不需要升级&#xff09; 添加新元素到 intset (需要升级) 4、升级 升级实例 5、关于升级 …

GZ015 机器人系统集成应用技术样题4-学生赛

2023年全国职业院校技能大赛 高职组“机器人系统集成应用技术”赛项 竞赛任务书&#xff08;学生赛&#xff09; 样题4 选手须知&#xff1a; 本任务书共 25页&#xff0c;如出现任务书缺页、字迹不清等问题&#xff0c;请及时向裁判示意&#xff0c;并进行任务书的更换。参赛队…

Flutter在Android Studio上创建项目与构建模式

一、安装插件 1、前提条件&#xff0c;安装配置好Android Studio环境 2、安装Flutter和Dart插件 Linux或者Windows平台&#xff1a; 1&#xff09;、打开File > Settings。 2&#xff09;、在左侧列表中&#xff0c;选择"Plugins"右侧上方面板选中 "Market…

vue-element-admin如何把mock换成使用真实后台接口

1&#xff09;修改vue.config.js文件 use strict const path require(path) const defaultSettings require(./src/settings.js)function resolve(dir) {return path.join(__dirname, dir) }const name defaultSettings.title || vue Element Admin // page title// If you…

Vue3-16-【v-model】 表单数据绑定

作用描述 v-model 指令&#xff0c;实现了 表单输入组件的值 与 js 中的变量的值的绑定关系。 当我们在页面上执行输入动作时&#xff0c;js中变量的值也会同步发生变化。表单不仅仅局限于输入框&#xff0c;其他的如 &#xff1a; 单选按钮&#xff0c;复选框&#xff0c;下拉…

数据结构面试题和题目解析

以下是一些数据结构的面试题和解析&#xff1a; 1. 什么是链表&#xff1f; 链表是一种线性数据结构&#xff0c;由一系列节点组成&#xff0c;每个节点包含数据部分和指向下一个节点的指针。链表的主要优点是插入和删除操作比较方便&#xff0c;但访问链表中的元素不如访问数组…

【INTEL(ALTERA)】 quartus SignalTap 逻辑分析器 – Nios® II 插件 无法检测 Nios® II/f 处理器内核

说明 使用 Nios II 插件将 Nios II/f 处理器内核节点添加到 SignalTap 逻辑分析器时&#xff0c;在 英特尔 Quartus Prime Pro Edition 软件 23.3 版中可能会出现此问题。 错误消息&#xff1a; 无法完成“添加带插件的节点”命令&#xff0c;因为在当前设计中找不到所选 IP。…

IDEA之设置项目包的结构层级为eclipse默认样式

idea默认项目包的结构层级如下: 想修改成eclipse默认的那种样式&#xff0c;设置步骤如下: 1.点击下图中红框图标进行设置 2.选择 Tree Appearance&#xff0c;取消勾选 Compact Middle Packages 3.勾选红框里的两个选项&#xff0c;Flatten Packages 和 Hide Empty Middle Pa…

DL Homework 11

由于好多同学问我要代码&#xff0c;但这两天光顾着考四六级了&#xff0c;所以只能今天熬夜先给赶出来&#xff0c;第一题先搁置&#xff0c;晚点补上&#xff0c;先写第二题 习题6-4 推导LSTM网络中参数的梯度&#xff0c; 并分析其避免梯度消失的效果 习题6-3P 编程实现…

KMP算法, 什么是KMP算法 ,暴力匹配 ,KMP算法实现

文章目录 KMP算法什么是KMP算法暴力匹配KMP算法实现 KMP算法 什么是KMP算法 KMP是Knuth、Morris和Pratt首字母的缩写&#xff0c;KMP也是由这三位学者发明&#xff08;1977年联合发表论文&#xff09;。 KMP主要应用在字符串的匹配&#xff0c;是一个解决模式串在文本串是否…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《耦合碳-绿证-消纳量市场的日前电量市场交易交互式优化》

这个标题描述了一种优化模型或算法&#xff0c;用于在日前电量市场中耦合碳排放权市场、可再生能源绿色证书市场和消纳量市场进行交易的交互式优化。我将解析标题的关键词和概念&#xff1a; 日前电量市场&#xff1a;指的是电力市场中进行短期调度和交易的市场&#xff0c;其…

Idea maven打包时 报错 illegalArgumentException: Malformed \uxxxx encoding 解决方法

1 改变打包命令重新打包 在maven打包命令上加入 -e -X 2 找到报错类和方法 可以看到是 java.util.Properties#loadConvert类方法中有个throw new IllegalArgumentException( "Malformed \\uxxxx encoding.")&#xff0c;在此打断点 3 以Debug方式重新运行maven…

SLAM算法与工程实践——相机篇:传统相机使用(1)

SLAM算法与工程实践系列文章 下面是SLAM算法与工程实践系列文章的总链接&#xff0c;本人发表这个系列的文章链接均收录于此 SLAM算法与工程实践系列文章链接 下面是专栏地址&#xff1a; SLAM算法与工程实践系列专栏 文章目录 SLAM算法与工程实践系列文章SLAM算法与工程实践…

死锁的预防、避免、检测和消除

一、预防死锁 1. 破坏互斥条件 2. 破坏不剥夺条件 3.破坏请求和保持条件 4.破坏循环等待条件 二、避免死锁 避免死锁的一种方法是使用银行家算法&#xff0c;它涉及到安全序列的概念。银行家算法是一种资源分配和死锁避免的算法&#xff0c;它确保系统能够分配资源而不会导致死…

STM32迪文屏图标控件保姆级教程

要主图的去末尾&#xff0c;末尾福利图在等着你~~~ 文章目录 前言 开发环境 二、使用步骤 1.添加图标控件 2.设置图标属性 3.图标库ICL文件生成 4.单片机程序编写 容易踩得坑 一、前言 本篇文章主要介绍了在DGBUS平台上使用图标变量的步骤。首先需要在DGBUS中添加一个图标变量控…

linux(centos7)mysql8.0主从集群搭建(两台机器)

docker安装:&#xff08;转载&#xff09;centos7安装Docker详细步骤&#xff08;无坑版教程&#xff09;-CSDN博客 环境信息 主数据库服务器&#xff1a;192.168.1.10 从数据库服务器&#xff1a;192.168.1.11 1. mysql8.0镜像下载 docker pull mysql:8.0.23 2.创建docke…