通信网络编程2.0——JAVA

一、传统阻塞式 I/O 模型

实现简易多人聊天系统:服务端与客户端

服务端

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

定义了 ChatServer 类,指定服务器端口为 6666 ,创建 ServerSocket 对象用于监听客户端连接,采用 CopyOnWriteArrayList 存储已连接的客户端 Socket 对象。相较于普通列表,CopyOnWriteArrayList 在迭代时会复制整个底层数组,确保在遍历过程中其他线程对集合的修改不会影响当前遍历,有效避免并发修改异常。

public void initServer() {// 初始化服务器的方法try {ss = new ServerSocket(port);// 创建 ServerSocket 对象并绑定到指定端口System.out.println("服务器启动,等待客户端连接...");} catch (IOException e) {throw new RuntimeException(e);}}

 initServer 方法用于创建 ServerSocket 对象并绑定到指定端口,启动服务器端,等待客户端连接。

public void listenerConnection() {// 监听客户端连接的方法,返回连接的 Socket 对象new Thread(()->{while(true){try {Socket socket = ss.accept();// 调用 accept() 方法等待客户端连接//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 方法启动一个线程,通过 ServerSocketaccept() 方法等待客户端连接。当有客户端连接时,将其 Socket 对象添加到客户端列表中,并输出客户端 IP 地址。

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 msgLen = is.read();// 读取消息内容长度的字节if(msgLen == 0){continue;}byte[] msg = new byte[msgLen];// 根据读取的长度创建字节数组存储消息内容is.read(msg);// 读取消息内容字节数组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 方法用于读取客户端发送的消息。创建一个线程,持续读取每个客户端的输入流。读取消息时,先读取发送方名称长度、名称字节数组,再读取消息内容长度、内容字节数组,将其转换为字符串并显示在消息区域。然后将消息转发给所有其他客户端。

public void start() {// 启动服务器的方法initServer();// 调用初始化服务器的方法//new Thread(()->{//startSend();// 启动服务端从控制台向所有客户端发送消息的线程//}).start();ChatUI ui = new ChatUI("服务端", clientSockets);ui.setVisible(true); // 确保 UI 可见listenerConnection();// 调用监听客户端连接的方法readMsg(clientSockets,ui.msgShow);// 调用读取消息的方法}

start 方法初始化服务器,创建服务端界面,启动监听客户端连接和读取消息的功能。

客户端

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;}
}

 定义了 Client 类,包含 Socket 对象用于连接服务器,以及服务器的 IP 地址和端口号。构造方法用于初始化客户端 IP 地址和端口号。

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);}}

connectServer 方法用于连接服务器。创建 Socket 对象连接到指定 IP 地址和端口号的服务器,获取输入流和输出流。然后向服务器发送用户名长度和用户名字节数组,完成连接。

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();}

 readMsg 方法用于读取服务器发送的消息。创建一个线程,读取发送方名称长度、名称字节数组,消息内容长度、内容字节数组,将其转换为字符串并显示在消息区域。

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();}

startClient 方法启动客户端。通过对话框获取用户名,连接服务器,创建客户端界面,启动读取消息的功能。同时创建一个线程,定期向服务器发送心跳包(0),保持连接。

图形界面

服务端界面

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);}}}}

 服务端事件监听器实现了 ActionListener 接口。当点击发送按钮时,获取输入的消息文本,将其添加到显示区域。然后遍历所有客户端 Socket,获取每个客户端的输出流,发送用户名长度、用户名、消息内容长度、消息内容给每个客户端。

客户端事件监听器

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);}}
}

客户端事件监听器实现了 ActionListener 接口。当点击发送按钮时,获取输入的消息文本,将其添加到显示区域。然后通过客户端的输出流向服务器发送用户名长度、用户名、消息内容长度、消息内容。

 

运行效果

二、NIO 模型

聊天服务器

public class NIOChatServer {private static final int PORT = 8080;private static final Charset charset = Charset.forName("UTF-8");private static Set<SocketChannel> clients = new HashSet<>();public static void main(String[] args) throws IOException {Selector selector = null;try {selector = Selector.open();ServerSocketChannel serverChannel = ServerSocketChannel.open();serverChannel.bind(new InetSocketAddress(PORT));serverChannel.configureBlocking(false);serverChannel.register(selector, SelectionKey.OP_ACCEPT);} catch (IOException e) {throw new RuntimeException(e);}System.out.println("Server started on port " + PORT);while (true){try {selector.select();} catch (IOException e) {throw new RuntimeException(e);}Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();while (keyIterator.hasNext()){SelectionKey key = keyIterator.next();keyIterator.remove();if (key.isAcceptable()){handleAccept(key,selector);}else if (key.isReadable()){handleRead(key);}}}}
}

在服务器端代码中,首先创建一个选择器(Selector), 它是 NIO 中用于监听多个通道事件的核心组件。然后打开一个服务器套接字通道(ServerSocketChannel),绑定到指定端口(8080),并将其设置为非阻塞模式。接着将通道注册到选择器上,监听连接接受事件(SelectionKey.OP_ACCEPT)。启动一个无限循环,调用选择器的 select() 方法,该方法会阻塞直到有通道事件发生,然后迭代处理每个事件。

private static void handleAccept(SelectionKey key, Selector selector) throws IOException{ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();SocketChannel clientChannel = serverChannel.accept();clientChannel.configureBlocking(false);clientChannel.register(selector, SelectionKey.OP_READ);clients.add(clientChannel);System.out.println("New Client connected to " + clientChannel.getRemoteAddress());}

当检测到客户端连接事件时,服务器通道(ServerSocketChannel)调用 accept() 方法接受新连接,返回新的客户端套接字通道(SocketChannel)。将客户端通道设置为非阻塞模式,并注册到选择器上,监听读事件(SelectionKey.OP_READ),以便后续接收该客户端的消息。同时将客户端通道添加到 clients 集合中,用于后续广播消息。

private static void handleRead(SelectionKey key) throws IOException {SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = 0;try {bytesRead = clientChannel.read(buffer);if (bytesRead == -1){disconnectClient(clientChannel);return;}buffer.flip();String message = charset.decode(buffer).toString();System.out.println("Received: " + message);broadcastMessage(message,clientChannel);} catch (IOException e) {disconnectClient(clientChannel);}}

 当检测到读事件时,表示客户端有数据可读。创建一个缓冲区(ByteBuffer)用于存储从客户端读取的数据。调用客户端通道的 read() 方法将数据读取到缓冲区。如果返回值为 -1,表示客户端断开连接,调用 disconnectClient() 方法处理断开事件。否则,将缓冲区翻转(flip),解码缓冲区中的字节数据为字符串,并调用 broadcastMessage() 方法将消息广播给其他客户端。

private static void broadcastMessage(String message, SocketChannel sender) throws IOException {ByteBuffer buffer = charset.encode(message);for (SocketChannel client : clients) {if(client != sender && client.isConnected()){client.write(buffer);buffer.rewind();}}}

将消息字符串编码为字节缓冲区,然后遍历所有客户端通道。对于每个客户端通道(除了发送消息的客户端),如果通道处于连接状态,就将缓冲区中的数据写入通道,实现消息的广播。写完后调用 rewind() 方法重置缓冲区的位置,以便下次写操作从头开始。

private static void disconnectClient(SocketChannel client) throws IOException {clients.remove(client);System.out.println("Client disconnected from " + client.getRemoteAddress());client.close();}

 从 clients 集合中移除断开连接的客户端通道,关闭该通道,并打印断开连接的日志。

聊天客户端

public class BlockingChatClient {public static void main(String[] args) {Socket socket = null;try {socket = new Socket("localhost", 8080);System.out.println("Connected");// 创建输入输出流BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));// 启动一个线程来读取服务器发送的消息new Thread(() -> {String message;try {while ((message = reader.readLine()) != null) {System.out.println("[Server] " + message);}} catch (IOException e) {System.out.println("Disconnected from server");}}).start();// 从控制台读取用户输入并发送到服务器BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));String input;while ((input = consoleReader.readLine()) != null) {writer.write(input);writer.newLine();writer.flush();if ("/exit".equalsIgnoreCase(input)) {break;}}} catch (IOException e) {e.printStackTrace();} finally {try {if (socket != null) {socket.close();}} catch (IOException e) {e.printStackTrace();}}}
}

客户端代码使用传统的阻塞式 I/O。启动时连接到服务器,建立 Socket 连接。然后获取输入流和输出流。启动一个线程读取服务器发送的消息并打印到控制台。在主线程中,从控制台读取用户输入,发送到服务器。当输入 "/exit" 时,退出客户端。最后确保关闭 Socket 连接。 

运行效果

三、两种模型对比

I/O模型

  • 资源消耗大 :每个客户端都需要一个独立线程,大量客户端连接会导致线程数量剧增,增加系统资源消耗和线程切换开销。

  • 扩展性差 :线程数量受限于系统资源,难以处理高并发场景。

NIO 模型

  • 高并发支持 :一个线程可管理多个客户端连接,显著降低资源消耗,提升系统并发能力。

  • 高效复用 :通过选择器复用线程,减少线程创建和销毁的开销。

对比方面传统阻塞式 I/ONIO 模型
线程模型一客户端一线程,资源占用大,线程切换频繁一线程多客户端,资源占用小,线程切换少
并发能力并发能力受限于线程数量支持高并发,可处理大量客户端连接
阻塞处理依赖线程隔离防止阻塞通过非阻塞 I/O 和多路复用防止阻塞
适用场景适合客户端数量较少,对响应时间要求不高的场景适合高并发场景,如即时通讯、在线游戏等

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

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

相关文章

(转)什么是DockerCompose?它有什么作用?

一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用&#xff0c;而无需手动一个个创建和运行容器。 Compose文件是一个文本文件&#xff0c;通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …

Python打卡第51天

浙大疏锦行 作业&#xff1a; day43的时候我们安排大家对自己找的数据集用简单cnn训练&#xff0c;现在可以尝试下借助这几天的知识来实现精度的进一步提高 import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader from tor…

Notepad++ 官方下载

https://notepad-plus-plus.org/downloads/ 下载官网 1、https://github.com/notepad-plus-plus/notepad-plus-plus/releases 2、https://notepad-plus-plus.org/news/v881-we-are-with-ukraine/

运维之十个问题--2

目录 1. 如果有ip恶意刷流量怎么办 2. 标准端口范围 3.内存16G&#xff0c;交换分区多大 4.请简述非对称加密算法&#xff0c;ping命令通过什么协议实现&#xff0c;icmp是什么协议 5.客户访问网站速度慢原因 6. 进程和线程的区别 7.zabbix监控是你搭建的吗&#xff0c;平…

vue前端面试题——记录一次面试当中遇到的题(1)

1.v-if和v-show的区别 v-if和v-show都是Vue中用于条件渲染的指令&#xff0c;但它们的实现机制和适用场景有所不同&#xff1a; v-if是真正的条件渲染&#xff0c;在条件切换时会销毁和重建DOM元素&#xff0c;适合运行时条件变化不频繁的场景&#xff1b; v-show只是通过CS…

【QT面试题】(三)

文章目录 Qt信号槽的优点及缺点Qt中的文件流和数据流区别&#xff1f;Qt中show和exec区别QT多线程使用的方法 (4种)QString与基本数据类型如何转换&#xff1f;QT保证多线程安全事件与信号的区别connect函数的连接方式&#xff1f;信号与槽的多种用法Qt的事件过滤器有哪些同步和…

Vscode下Go语言环境配置

前言 本文介绍了vscode下Go语言开发环境的快速配置&#xff0c;为新手小白快速上手Go语言提供帮助。 1.下载官方Vscode 这步比较基础&#xff0c;已经安装好的同学可以直接快进到第二步 官方安装包地址&#xff1a;https://code.visualstudio.com/ 双击一直点击下一步即可,记…

HTML 文本省略号

目录 HTML 文本省略号超行省略号如何实现1. 单行文本溢出显示省略号2. 多行文本溢出显示省略号方法一&#xff1a;使用 -webkit-line-clamp&#xff08;推荐&#xff09;方法二&#xff1a;使用伪元素&#xff08;兼容性好&#xff09;方法三&#xff1a;使用 JavaScript 动态监…

Spring Boot 实现流式响应(兼容 2.7.x)

在实际开发中&#xff0c;我们可能会遇到一些流式数据处理的场景&#xff0c;比如接收来自上游接口的 Server-Sent Events&#xff08;SSE&#xff09; 或 流式 JSON 内容&#xff0c;并将其原样中转给前端页面或客户端。这种情况下&#xff0c;传统的 RestTemplate 缓存机制会…

ffmpeg 新版本转码设置帧率上限

ffmpeg 新版本转码设置帧率上限 ffmpeg 在老版本比如 4.3的时候&#xff0c;转码设置帧率上限是通过vsync控制 # 设置动态控制最大帧率60 "-vsync 2 -r 60" 新版本这个参数没办法动态判断控制帧率了 替换为使用filter中的fps进行设置 # 设置动态帧率最大60帧 -…

Qt绘制电池图标源码分享

一、效果展示 二、源码分享 cell.h #ifndef CELL_WIDGET_H #define CELL_WIDGET_H #include <QWidget> #include <QPainter> #include <QPaintEngine> #include <QPaintEvent>/* 电池控件类 */ class CellWidget : public QWidget {Q_OBJECTQ_PROPERTY…

安卓基础(生成APK)

​​生成调试版&#xff08;Debug&#xff09;​​ Build → Build Bundle(s)/APK(s) → Build APK输出路径&#xff1a;app/build/outputs/apk/debug/app-debug.apk ​​生成发布版&#xff08;Release&#xff09;​​ Build → Generate Signed Bundle/APK → 选择 ​​APK​…

如何在 TypeScript 中使用类型保护

前言 类型保护是一种 TypeScript 技术&#xff0c;用于获取变量类型的信息&#xff0c;通常用于条件块中。类型保护是返回布尔值的常规函数​​&#xff0c;它接受一个类型并告知 TypeScript 是否可以将其缩小到更具体的值。类型保护具有独特的属性&#xff0c;可以根据返回的…

山东大学软件学院项目实训-基于大模型的模拟面试系统-面试对话标题自动总结

面试对话标题自动总结 主要实现思路&#xff1a;每当AI回复用户之后&#xff0c;调用方法查看当前对话是否大于三条&#xff0c;如果大于则将用户的两条和AI回复的一条对话传给DeepSeek让其进行总结&#xff08;后端&#xff09;&#xff0c;总结后调用updateChatTopic进行更新…

Spring Cloud与Alibaba微服务架构全解析

Spring Cloud与Spring Cloud Alibaba微服务架构解析 1. Spring Boot概念 Spring Boot并不是新技术&#xff0c;而是基于Spring框架下“约定优于配置”理念的产物。它帮助开发者更容易、更快速地创建独立运行和产品级别的基于Spring框架的应用。Spring Boot中并没有引入新技术…

AI 赋能 Java 开发:从通宵达旦到高效交付的蜕变之路

作为一名深耕 Java 开发领域多年的从业者&#xff0c;相信很多同行都与我有过相似的经历&#xff1a;在 “996” 甚至 “007” 的高压模式下&#xff0c;被反复修改的需求、复杂的架构设计、无休止的代码编写&#xff0c;以及部署时层出不穷的问题折磨得疲惫不堪。长期以来&…

06. C#入门系列【自定义类型】:从青铜到王者的进阶之路

C#入门系列【自定义类型】&#xff1a;从青铜到王者的进阶之路 一、引言&#xff1a;为什么需要自定义类型&#xff1f; 在C#的世界里&#xff0c;系统自带的类型&#xff08;如int、string、bool&#xff09;就像是基础武器&#xff0c;能解决一些简单问题。但当你面对复杂的…

使用 PyTorch 和 TensorBoard 实时可视化模型训练

在这个教程中&#xff0c;我们将使用 PyTorch 训练一个简单的多层感知机&#xff08;MLP&#xff09;模型来解决 MNIST 手写数字分类问题&#xff0c;并且使用 TensorBoard 来可视化训练过程中的不同信息&#xff0c;如损失、准确度、图像、参数分布和学习率变化。 步骤 1&…

第十五章 15.OSPF(CCNA)

第十五章 15.OSPF(CCNA) 介绍了大家都能用的OSPF动态路由协议 注释&#xff1a; 学习资源是B站的CCNA by Sean_Ning CCNA 最新CCNA 200-301 视频教程(含免费实验环境&#xff09; PS&#xff1a;喜欢的可以去买下他的课程&#xff0c;不贵&#xff0c;讲的很细 To be cont…

手机连接windows遇到的问题及解决方法

文章目录 写在前面一、手机与windows 连接以后 无法在win端打开手机屏幕,提示801方法零、检查连接方法一、系统修复方法二、断开重连方法三、软件更新方法四、关闭防火墙 写在前面 本文主要记录所遇到的问题以及解决方案&#xff0c;以备后用。 所用机型&#xff1a;win11 专业…