主要添加了私聊功能

1服务器类定义与成员变量

public class ChatServer {int port = 6666;// 定义服务器端口号为 6666ServerSocket ss;// 定义一个 ServerSocket 对象用于监听客户端连接//List<Socket> clientSockets = new ArrayList<>();// 定义一个列表用于存储已连接的客户端 Socket 对象List<Socket> clientSockets = new CopyOnWriteArrayList<>();List<String> clientNames = new ArrayList<>(10);//迭代时会复制整个底层数组,因此在遍历过程中其他线程对集合的修改不会影响当前遍历,// 有效避免了 ConcurrentModificationException 异常。
}

  • 端口号:服务器的端口号被定义为 6666,客户端需要知道这个端口号才能与服务器建立连接。
  • ServerSocket 对象ServerSocket 是 Java 提供的一种用于服务器端编程的类,它负责监听特定端口上的客户端连接请求。一旦有客户端连接,就会创建一个新的 Socket 对象来处理与该客户端之间的通信。
  • 客户端套接字列表clientSockets 使用了 CopyOnWriteArrayList 来存储与客户端建立连接的 Socket 对象。这种数据结构在遍历时会复制整个底层数组,因此在遍历过程中其他线程对集合的修改不会影响当前遍历,有效避免了 ConcurrentModificationException 异常。
  • 客户端名称列表clientNames 用于存储每个连接客户端的名称,方便在消息中区分不同的客户端。

2初始化服务器

public void initServer() {// 初始化服务器的方法try {ss = new ServerSocket(port);// 创建 ServerSocket 对象并绑定到指定端口System.out.println("服务器启动,等待客户端连接...");} catch (IOException e) {throw new RuntimeException(e);}}
  •  在服务器初始化方法 initServer 中,通过调用 ServerSocket 的构造函数并传入端口号,创建了一个 ServerSocket 对象,绑定了服务器到指定端口,使服务器能够监听该端口上的客户端连接请求。一旦初始化成功,就会打印出 "服务器启动,等待客户端连接..." 的提示信息,告知服务器已正常启动并处于监听状态。

3监听客户端连接

public void listenerConnection() {// 监听客户端连接的方法,返回连接的 Socket 对象new Thread(()->{while(true){try {Socket socket = ss.accept();// 调用 accept() 方法等待客户端连接clientNames.add("Hello");//clientSockets.add(socket);synchronized (clientSockets) {// 同步操作确保线程安全clientSockets.add(socket);// 将连接的客户端 Socket 对象添加到列表中}System.out.println("客户端已连接:" + socket.getInetAddress().getHostAddress());// 输出客户端连接成功提示信息及客户端 IP 地址} catch (IOException e) {throw new RuntimeException(e);}}}).start();}
  • listenerConnection 方法创建了一个新线程,用于不断地监听客户端的连接请求。在循环中,调用 ss.accept() 方法阻塞等待客户端的连接。一旦有客户端连接,就会创建一个新的 Socket 对象,并将其添加到 clientSockets 列表中。同时,向 clientNames 列表中添加一个默认客户端名称 "Hello",后续可以根据实际情况更新该名称。每当有新的客户端连接成功,就会打印出客户端的 IP 地址,表明该客户端已成功连接到服务器。

4读取客户端消息

public void readMsg(List<Socket> clientSockets, JTextArea msgShow) {// 读取客户端消息的方法//System.out.println("clientSockets size: " + clientSockets.size()); // 检查列表大小synchronized (clientSockets) {// 对客户端列表进行同步操作Thread tt = new Thread(() -> {// 创建一个线程用于读取并处理客户端消息//System.out.println("开始读取客户端发送的消息");while (true) {// 无限循环持续读取消息InputStream is;// 定义输入流对象用于读取客户端消息Socket socket = null;try {Thread.sleep(50);} catch (InterruptedException e) {throw new RuntimeException(e);}for (Socket cSocket : clientSockets) {// 遍历每个客户端 Socket//System.out.println("循环每个socket");socket = cSocket;if(socket == null){continue;}try {is = socket.getInputStream();// 获取客户端 Socket 对象的输入流} catch (IOException e) {throw new RuntimeException(e);}try {int idLen = is.read();// 读取消息中发送方名称长度的字节if(idLen == 0){continue;}byte[] id = new byte[idLen];// 根据读取的长度创建字节数组存储发送方名称is.read(id);// 读取发送方名称字节数组int si = clientSockets.indexOf(socket);clientNames.set(si,new String(id));int msgLen = is.read();// 读取消息内容长度的字节if(msgLen == 0){continue;}byte[] msg = new byte[msgLen];// 根据读取的长度创建字节数组存储消息内容is.read(msg);// 读取消息内容字节数组String clientmsg = new String(msg);//先判断@有没有int start = clientmsg.indexOf('@');if(start != -1){int end = clientmsg.indexOf(' ');String friendID = clientmsg.substring(start+1,end);String message = clientmsg.substring(end);System.out.println(new String(id) + "发送给" + friendID + " 的一条私聊消息:" + message);// 将字节数组转换为字符串并输出消息内容msgShow.append(new String(id) + "发送给" + friendID + " 的一条私聊消息:" + message + "\n");for (Socket clientSocket : clientSockets) {// 遍历所有已连接的客户端 Socket 对象if (clientSocket == socket) {// 如果是当前发送消息的客户端continue;}int s = clientSockets.indexOf(clientSocket);String name = clientNames.get(s);if(name.equals(friendID)){OutputStream os = null;// 定义输出流对象用于向其他客户端发送消息os = clientSocket.getOutputStream();// 获取客户端 Socket 对象的输出流os.write(id.length);// 发送发送方名称长度os.write(id);// 发送发送方名称字节数组message += "(这是一条私聊消息)";os.write(message.getBytes().length);// 发送消息内容长度os.write(message.getBytes());// 发送消息内容字节数组os.flush();// 刷新输出流确保数据发送完成break;}}}else {System.out.println(new String(id) + "发送的消息:" + new String(msg));// 将字节数组转换为字符串并输出消息内容msgShow.append(new String(id) + "说:" + new String(msg) + "\n");// 转发信息给所有其他客户端for (Socket clientSocket : clientSockets) {// 遍历所有已连接的客户端 Socket 对象if (clientSocket == socket) {// 如果是当前发送消息的客户端continue;}OutputStream os = null;// 定义输出流对象用于向其他客户端发送消息os = clientSocket.getOutputStream();// 获取客户端 Socket 对象的输出流os.write(id.length);// 发送发送方名称长度os.write(id);// 发送发送方名称字节数组os.write(msg.length);// 发送消息内容长度os.write(msg);// 发送消息内容字节数组os.flush();// 刷新输出流确保数据发送完成}}} catch (IOException e) {throw new RuntimeException(e);}}}});tt.start();}}
  • readMsg 方法的核心功能是读取客户端发送的消息,并根据消息类型(普通消息或私聊消息)进行相应的处理和转发。
  • 首先,创建一个新线程 tt,在无限循环中依次检查 clientSockets 列表中的每个 Socket 对象。对于每个客户端 Socket,通过 socket.getInputStream() 获取输入流,从而读取客户端发送的数据。
  • 客户端发送的数据格式预先约定为:首先是一个字节表示发送方名称的长度,然后是发送方名称对应的字节数组;接下来是一个字节表示消息内容长度,最后是消息内容对应的字节数组。按照这种格式,先读取发送方名称长度 idLen,根据该长度创建字节数组 id 读取发送方名称,再读取消息内容长度 msgLen,创建字节数组 msg 读取消息内容。
  • 随后,将读取到的发送方名称更新到对应的 clientNames 列表位置。接下来对消息内容进行判断,如果消息内容中包含 "@" 符号,则认为这是一条私聊消息。通过解析消息内容,获取私聊目标的名称 friendID 和实际消息内容 message,然后在 clientSockets 中查找对应的私聊目标客户端 Socket,并将私聊消息发送给该目标客户端。
  • 如果消息内容中不包含 "@" 符号,则认为这是一条普通消息,直接将消息转发给除发送方外的所有其他客户端。
  • 无论是普通消息还是私聊消息,都通过输出流 OutputStream 将消息发送给目标客户端。发送时,同样按照约定的数据格式,先发送发送方名称的长度和名称字节数组,再发送消息内容的长度和内容字节数组,并调用 os.flush() 刷新输出流确保数据发送完成。

5服务器启动

public void start() {// 启动服务器的方法initServer();// 调用初始化服务器的方法//new Thread(()->{//startSend();// 启动服务端从控制台向所有客户端发送消息的线程//}).start();ChatUI ui = new ChatUI("服务端", clientSockets);ui.setVisible(true); // 确保 UI 可见listenerConnection();// 调用监听客户端连接的方法readMsg(clientSockets,ui.msgShow);// 调用读取消息的方法}
  • start 方法中,首先调用 initServer 方法初始化服务器,然后创建一个 ChatUI 对象作为服务器端的用户界面(假设存在 ChatUI 类,用于展示聊天内容等信息),使用户界面可见。接着调用 listenerConnection 方法启动监听客户端连接的线程,最后调用 readMsg 方法启动读取消息的线程,并将读取到的消息显示在用户界面中。

6主方法

public static void main(String[] args) {ChatServer server = new ChatServer();// 创建 ChatServer 对象server.start();// 调用启动服务器的方法}
  • main 方法作为程序的入口,创建了一个 ChatServer 对象,并调用其 start 方法启动服务器。 

7其他模块代码不变

public class Client {Socket socket;// 定义 Socket 对象用于与服务器建立连接String ip;// 定义服务器 IP 地址int port;// 定义服务器端口号InputStream in;// 定义输入流对象用于读取服务器发送的消息OutputStream out;// 定义输出流对象用于向服务器发送消息public Client(String ip, int port) {// 构造方法,初始化客户端 IP 地址和端口号this.ip = ip;this.port = port;}public void connectServer(String userName) {// 连接服务器的方法try {socket = new Socket(ip, port);// 创建 Socket 对象连接到指定 IPin = socket.getInputStream();// 获取 Socket 对象的输入流用于读取消息out = socket.getOutputStream();// 获取 Socket 对象的输出流用于发送消息try {out.write(userName.length());out.write(userName.getBytes());out.flush();} catch (IOException e) {throw new RuntimeException(e);}System.out.println("连接服务器成功");} catch (IOException e) {throw new RuntimeException(e);}}public void readMsg(JTextArea msgShow) {// 读取服务器发送的消息的方法new Thread(() -> {// 创建一个线程用于读取并处理服务器消息try {System.out.println("开始读取消息");while (true) { // 无限循环持续读取消息int senderNameLength = in.read();// 读取发送方名称长度的字节byte[] senderNameBytes = new byte[senderNameLength];// 根据读取的长度创建字节数组存储发送方名称in.read(senderNameBytes);// 读取发送方名称字节数组int msgLength = in.read();// 读取消息内容长度的字节byte[] msgBytes = new byte[msgLength];// 根据读取的长度创建字节数组存储消息内容in.read(msgBytes);// 读取消息内容字节数组System.out.println(new String(senderNameBytes) + "发送的消息:" + new String(msgBytes));// 将字节数组转换为字符串并输出消息内容msgShow.append(new String(senderNameBytes) +"说:" + new String(msgBytes) + "\n");}} catch (IOException e) {throw new RuntimeException(e);}}).start();}/*public void startSend() {// 从控制台向服务器发送消息的方法new Thread(() -> {// 创建一个线程用于读取控制台输入并发送消息try {BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));// 创建缓冲读取器读取控制台输入System.out.println("请输入用户名:");String userName = reader.readLine();// 读取一行控制台输入作为用户名System.out.println("请输入消息(按回车发送):");while (true) {// 无限循环持续读取并发送消息String msg = reader.readLine();// 读取一行控制台输入作为消息内容if (msg != null && !msg.isEmpty()) {// 如果输入的消息不为空out.write(userName.getBytes().length);// 发送用户名长度out.write(userName.getBytes());// 发送用户名字节数组out.write(msg.getBytes().length);// 发送消息内容长度out.write(msg.getBytes());// 发送消息内容字节数组out.flush();// 刷新输出流确保数据发送完成}}} catch (IOException e) {throw new RuntimeException(e);}}).start();}*/public void startClient() {// 启动客户端的方法String userName = JOptionPane.showInputDialog("请输入用户名:");connectServer(userName);// 调用连接服务器的方法ChatUI ui = new ChatUI(userName, out);readMsg(ui.msgShow);// 调用读取消息的方法//startSend();// 调用发送消息的方法try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}new Thread() {public void run() {while (true) {try {out.write(0);out.flush();Thread.sleep(500);} catch (IOException e) {throw new RuntimeException(e);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}.start();}public static void main(String[] args) {Client client = new Client("127.0.0.1", 6666);// 创建 Client 对象,连接到本地主机的 6666 端口client.startClient();// 调用启动客户端的方法}
}
public class ChatUI extends JFrame {public JTextArea msgShow = new JTextArea();// 显示消息的文本区域public ChatUI(String title, List<Socket> clientSockets) {// 服务器端构造方法super(title);// 设置窗口标题setSize(500, 500);// 设置窗口大小setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 设置关闭操作JScrollPane scrollPane = new JScrollPane(msgShow);// 创建滚动面板包括消息显示区域scrollPane.setPreferredSize(new Dimension(0, 350));add(scrollPane, BorderLayout.NORTH);// 添加到窗口北部// 创建消息输入面板及组件JPanel msgInput = new JPanel();JTextArea msg = new JTextArea();JScrollPane scrollPane1 = new JScrollPane(msg);scrollPane1.setPreferredSize(new Dimension(480, 80));msgInput.add(scrollPane1);JButton send = new JButton("发送");msgInput.add(send);msgInput.setPreferredSize(new Dimension(0, 120));add(msgInput, BorderLayout.SOUTH);// 添加到窗口南部setVisible(true);ChatListener cl = new ChatListener();// 创建事件监听器send.addActionListener(cl);// 为发送按钮添加监听器cl.showMsg = msgShow;// 传递消息显示组件cl.msgInput = msg;cl.userName = title;cl.clientSockets = clientSockets;}public ChatUI(String title, OutputStream out) {// 客户端构造方法super(title);setSize(500, 500);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);JScrollPane scrollPane = new JScrollPane(msgShow);scrollPane.setPreferredSize(new Dimension(0, 350));add(scrollPane, BorderLayout.NORTH);JPanel msgInput = new JPanel();JTextArea msg = new JTextArea();JScrollPane scrollPane1 = new JScrollPane(msg);scrollPane1.setPreferredSize(new Dimension(480, 80));msgInput.add(scrollPane1);JButton send = new JButton("发送");msgInput.add(send);msgInput.setPreferredSize(new Dimension(0, 120));add(msgInput, BorderLayout.SOUTH);setVisible(true);clientListener cl = new clientListener();send.addActionListener(cl);cl.showMsg = msgShow;cl.msgInput = msg;cl.userName = title;cl.out = out;}
}
public class ChatListener implements ActionListener {public List<Socket> clientSockets;// 客户端 Socket 列表JTextArea showMsg;// 消息显示区域JTextArea msgInput;// 消息输入区域String userName;// 用户名OutputStream out;// 输出流public void actionPerformed(ActionEvent e) {// 处理发送按钮点击事件String text = msgInput.getText();// 获取输入的消息文本showMsg.append(userName + ": " + text + "\n");// 在显示区域追加消息for (Socket cSocket : clientSockets) {// 遍历所有客户端Socket socket = cSocket;try {out = socket.getOutputStream();// 获取客户端输出流out.write(userName.getBytes().length);// 发送用户名长度out.write(userName.getBytes());// 发送用户名out.write(text.getBytes().length);// 发送消息内容长度out.write(text.getBytes());// 发送消息内容out.flush();// 刷新输出流} catch (IOException ex) {throw new RuntimeException(ex);}}}}
public class clientListener implements ActionListener {JTextArea showMsg;// 消息显示区域JTextArea msgInput;// 消息输入区域String userName;// 用户名OutputStream out;// 输出流public void actionPerformed(ActionEvent e) {// 处理发送按钮点击String text = msgInput.getText();// 获取输入消息showMsg.append(userName + ": " + text + "\n");// 显示消息try {out.write(userName.getBytes().length);// 发送用户名长度out.write(userName.getBytes());// 发送用户名out.write(text.getBytes().length);// 发送消息长度out.write(text.getBytes());// 发送消息内容out.flush();// 刷新输出流//msgInput.setText(""); // 清空输入框} catch (IOException ex) {throw new RuntimeException(ex);}}
}

8运行效果

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

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

相关文章

RediSearch `FT.CREATE` 完全参数指南 HASH/JSON 双写实战

1、索引与 Schema 速概 索引 (index) —— 倒排、前缀、向量、Geo … 元数据集合Schema —— 索引蓝图&#xff1a;定义字段、类型、权重、排序及存储策略FT.CREATE —— 创建索引命令&#xff0c;分「索引级参数」和「字段级参数」两层 2 、FT.CREATE 语法模板 FT.CREATE &…

QT学习教程(三十七)

系统繁忙时的响应&#xff08;Staying Responsive During Intensive Processing&#xff09; 当我们调用QApplication::exec()时&#xff0c;Qt 就开始了事件循环。启动时&#xff0c;Qt 发出显示和绘制事件&#xff0c;把控件显示出来。然后&#xff0c;事件循环就开始了&…

hot100 -- 17.技巧

1.多数元素 问题&#xff1a; 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 方法1&#xff1a; 哈希表 实时判断&#xff…

算法第39天| 打家劫舍 1、2、3

198. 打家劫舍 题目 思路与解法 class Solution { public:int rob(vector<int>& nums) {// dp数组含义&#xff1a;// 考虑下标i&#xff08;包括i&#xff09;以内的房屋&#xff0c;最多可以偷窃的金额为dp[i]if (nums.size() 0) return 0;if (nums.size() 1)…

车载CAN总线数据采集与故障诊断装置设计与实现

车载CAN总线数据采集与故障诊断装置设计与实现 链接:1.6W字 [下载]摘要1.1 研究背景1.2 研究意义(1)技术提升:推动CAN总线诊断的智能化与实时性(2)经济价值:降低诊断成本与维修时间(3)安全与标准化:促进车联网数据安全体系建设社会效益1.3 国内外研究现状1.3.1 国外研…

布瑞琳BRANEW:高端洗护领航者,铸就品质生活新典范

近日,布瑞琳BRANEW,这一中国高端洗护行业的领军品牌,再次凭借其卓越的服务品质、创新的经营模式以及对行业标准的深度推动,成为市场瞩目的焦点。作为北京2022年冬奥会和残奥会的商业服务保障单位,布瑞琳不仅展现了其无与伦比的服务能力,更在国际舞台上彰显了品牌的非凡影响力。…

AWS服务器扩充硬盘

1、在控制台上将需要扩充的硬盘增加空间 将硬盘大小由原来的50G升级到200G 2、登录所挂载的服务器 1&#xff09;查看硬盘分区情况 adminip-172-31-121-13:~$ sudo lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS nvme0n1 259:0 0 200G 0 disk ├─nv…

嵌入式自学第四十二天

PWM:脉冲宽度调制&#xff0c;调节电压为方波。关键参数&#xff1a;占空比、周期。 UART&#xff1a;通用异步收发器。 参与通信的设备&#xff1a;主机host 通信的本质&#xff1a;数据的传递。 通信方式&#xff1a; 单工&#xff1a;只能单向传递 半双工&#xff1a;双向…

人工智能如何重塑教育体系:个性化学习的新时代

&#x1f4dd;个人主页&#x1f339;&#xff1a;慌ZHANG-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 一、引言&#xff1a;教育的“智能革命”正在发生 教育作为人类社会发展的基石&#xff0c;始终紧随技术进步不断演化。从印刷术带来知识…

【云原生】基础篇

​一、云原生 1.1 本质与核心技术体系​ 云原生&#xff08;Cloud Native&#xff09;是以容器化、微服务、声明式API和动态编排为核心的架构范式&#xff0c;旨在最大化利用云的弹性、可观测性和自动化能力。其技术栈分层如下&#xff1a; ​1.2、云原生核心技术栈​ ​层级…

实时反欺诈:基于 Spring Boot 与 Flink 构建信用卡风控系统

在金融科技飞速发展的今天&#xff0c;信用卡欺诈手段日益高明和快速。传统的基于批处理的事后分析模式已难以应对实时性要求极高的欺诈场景。本文将详细介绍如何利用 Spring Boot 和 Apache Flink 这对强大的组合&#xff0c;构建一个高性能、可扩展的实时信用卡反欺诈系统。 …

通过apache共享文件

有时候&#xff0c;vmware虚拟机的vmware tools总是安装失败&#xff0c;这样就不能在虚拟机和主机之间共享文件。此时可以利用apache通过文件上传和下载共享文件。 通过下面的php文件&#xff0c;虚拟机作为客户端访问此php&#xff0c;可以在虚拟机和主机之间共享文件。当然…

Maven生命周期,测试

测试 Junit入门 单元测试 单元测试&#xff1a;就是针对最小的功能单元(方法)&#xff0c;编写测试代码对其正确性进行测试。 JUnit&#xff1a;最流行的Java测试框架之一&#xff0c;提供了一些功能&#xff0c;方便程序进行单元测试&#xff08;第三方公司提供&#xff09…

H5调试工具vconsole和Eruda对比

VConsole与Eruda对比分析 VConsole和Eruda是两款主流的移动端JavaScript调试工具&#xff0c;它们在功能定位、使用场景和技术实现上有诸多差异。以下从多个维度进行对比&#xff0c;帮助你选择更适合的工具&#xff1a; 一、核心功能对比 功能维度VConsoleEruda基础日志输出…

Java经典编程题

题目 1&#xff1a;斐波那契数列 题目要求&#xff1a;编写一个方法&#xff0c;输入正整数n&#xff0c;输出斐波那契数列的第n项。斐波那契数列的定义是&#xff1a;F(0)0&#xff0c;F(1)1, 当n > 1时&#xff0c;F(n)F(n - 1)F(n - 2)。 答案&#xff1a; public cla…

BUG调试案例五十:“低级”设计BUG案例篇(持续更新中.........)

引言 回头看这些年硬件路,总有一些“低级Bug”一次次地在给我上课。它们不是复杂的架构设计,不是玄妙的信号完整性问题,而是最基础、最应该避免、却又最容易忽略的小细节。 每一次Bug的背后,都是教训,有的甚至让整个项目差点“翻车”。写下这篇文章记录那些“看似简单实…

DeepSeek中的提示库及其用法示例

《DEEPSEEK原生应用与智能体开发实践 图书》【摘要 书评 试读】- 京东图书 为了深入探索DeepSeek提示词样例的丰富内涵&#xff0c;充分挖掘其背后潜藏的无限可能&#xff0c;同时致力于为用户打造更为卓越、便捷且高效的使用体验&#xff0c;DeepSeek官网的API文档匠心独运地…

Node.js特训专栏-实战进阶:7.Express模板引擎选型与使用

&#x1f525; 欢迎来到 Node.js 实战专栏&#xff01;在这里&#xff0c;每一行代码都是解锁高性能应用的钥匙&#xff0c;让我们一起开启 Node.js 的奇妙开发之旅&#xff01; Node.js 特训专栏主页 专栏内容规划详情 Express模板引擎选型与使用全解析&#xff1a;打造动态We…

uniapp评价组件

组件目录 components/Evaluation.vue <template><view class"evaluation-container"><!-- 综合评价 --><view class"evaluation-item" tap"parentTap"><text class"label label-1">综合评价</text&…

SQL Server2022版详细安装教程(Windows)

一&#xff0c;下载SQL Server 可以浏览器自己搜索一下 2、安装 安装前需要先将防火墙和带杀毒软件的先退出关闭掉&#xff08;防止安装不成功&#xff09; 2.1、选择自定义安装 2.2、更改位置进行安装 2.3、等待安装 3、进行安装配置 当安装好后会弹出一个这样的页面 3.1、…