一、前置技术

  • 项目介绍

    • 项目为局域网沟通软件,类似内网通,核心功能包括昵称输入、聊天界面展示在线人数(实时更新)、群聊,也可扩展私聊、登录注册、聊天记录存储等功能,结尾附GitHub链接。
  • 项目涉及技术

    • 包括GUI界面编程、网络通信、面向对象编程,以及字符串处理、时间获取等相关API。
  • 时间获取方案之JDK8之前的Date API

    • 通过创建Date对象获取此刻时间,但其格式为美式且不直观,需用SimpleDateFormat进行格式化。
    // 创建Date对象获取时间
    Date date = new Date();
    // 创建SimpleDateFormat对象指定格式
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    // 格式化时间
    String formattedDate = sdf.format(date);
    
  • 时间获取方案之JDK8的LocalDateTime

    • LocalDate获取年月日,LocalTime获取时分秒,LocalDateTime获取年月日时分秒,通过now()方法获取对象,支持纳秒级精度,且为不可变对象,线程安全,格式化需用DateTimeFormatter。
    // 获取LocalDateTime对象
    LocalDateTime now = LocalDateTime.now();
    // 创建DateTimeFormatter指定格式
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    // 格式化时间
    String formattedDateTime = now.format(dtf);
    
  • 字符串高效操作之StringBuilder

    • String因是不可变对象,大量拼接时性能差,而StringBuilder是可变对象,基于数组容器操作,通过append()方法高效拼接,支持链式编程,最后需用toString()转为String类型。
    // 创建StringBuilder对象
    StringBuilder sb = new StringBuilder();
    // 拼接字符串
    sb.append("张三").append("李四").append("王五");
    // 转为String类型
    String result = sb.toString();
    
  • 解决浮点型运算失真的BigDecimal

    • 用于解决小数运算结果失真问题,需通过字符串构造器或valueOf()方法(内部使用字符串构造器)创建对象,提供加减乘除等方法,除法时若结果除不尽需指定保留位数和舍入模式(如四舍五入)。
    // 创建BigDecimal对象
    BigDecimal a = new BigDecimal("0.1");
    BigDecimal b = BigDecimal.valueOf(0.2);
    // 加法运算
    BigDecimal sum = a.add(b);
    // 除法运算(保留2位小数,四舍五入)
    BigDecimal divide = a.divide(b, 2, RoundingMode.HALF_UP);
    // 转为double类型
    double result = sum.doubleValue();
    

二、AI获取客户端界面:

  • 项目需求分析:项目为局域网类沟通软件开发,启动界面只需输入聊天昵称,进入后显示在线人数,具备群聊功能,实时更新在线人数,先实现核心群聊功能,后续可扩展私聊等功能。
  • 技术选型:涉及GUI编程技术(swing)、网络编程、面向对象设计以及Java提供的常用API。
  • 项目步骤规划:第一步创建名为“it-chat”的模块;第二步获取系统所需界面(登录界面和聊天界面);第三步定义App启动类,创建并展示进入界面对象。
  • 获取登录界面:通过通义千问大模型生成局域网聊天进入界面代码,包含昵称输入框、进入和取消按钮,将代码复制到IDEA中,修改类名、处理乱码(将字体改为楷体),并测试界面能否启动。
  • 获取聊天界面:同样通过AI生成群聊界面代码,包含在线人数展示框、消息展示框、消息发送框和发送按钮,复制到IDEA后修改类名、调整窗口宽度、添加关闭窗口退出程序代码等,测试界面启动情况。
  • 定义启动类:在SRC下新建App类,在main方法中创建聊天进入界面对象并展示,完成界面准备工作,下面将从进入界面开始开发,串起所有功能。

三、架构分析

  • 系统整体架构及开发逻辑

    • 客户端与服务端通过管道连接实现通信,客户端发送的信息(如登录昵称、群聊消息)需经服务端转发给其他在线客户端。
    • 开发顺序:先分析系统整体架构,开发服务端,再完善客户端功能。
  • 服务端核心功能

    • 接收客户端的管道连接,支持多客户端同时接入。
    • 接收客户端发送的登录消息(含昵称)和群聊消息。
    • 存储所有在线客户端的socket管道,用于消息转发。
    • 收到登录消息后,更新所有客户端的在线人数列表。
    • 收到群聊消息后,将消息转发给所有在线客户端。
  • 服务端开发步骤及关键代码

    • 创建服务端项目,例如命名为“it-chat-server”。
    • 创建服务端启动类,负责启动服务端并等待客户端连接
    // 服务端启动类
    public class Server {public static void main(String[] args) {try {// 注册端口,端口从常量类获取ServerSocket serverSocket = new ServerSocket(Constant.PORT);System.out.println("服务端启动成功,等待客户端连接...");while (true) {// 等待客户端连接,获取管道Socket socket = serverSocket.accept();System.out.println("一个客户端连接成功!");// 将管道交给独立线程处理new ServerReaderThread(socket).start();// 将管道暂存(后续需结合登录消息存储昵称)// 此处仅为示意,实际需在接收登录消息后完善onlineSockets.put(socket, "未知用户");}} catch (IOException e) {e.printStackTrace();}}// 定义Map集合存储在线客户端管道及对应昵称public static Map<Socket, String> onlineSockets = new HashMap<>();
    }
    
    • 定义常量类存储端口信息
    // 常量类
    public class Constant {public static final int PORT = 6666; // 服务端端口
    }
    
    • 创建线程类处理客户端管道通信
    // 线程类处理客户端消息
    public class ServerReaderThread extends Thread {private Socket socket;public ServerReaderThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {// 后续将实现接收登录消息、群聊消息等逻辑System.out.println("线程开始处理客户端:" + socket);}
    }
    

四、服务端(在线人数模块)

  • 内容承接:已完成服务端基础开发,包括创建项目、接收客户端Socket管道并交由独立线程处理,同时准备了Map集合(onlineSockets)用于存储在线客户端的Socket及对应昵称(Socket为键,昵称为值)。

  • 服务端接收消息的类型及处理思路

    • 消息类型:登录消息、群聊消息、私聊消息、图片消息等。
    • 协议设计:客户端需先发送消息类型编号(如1代表登录、2代表群聊、3代表私聊),服务端通过编号区分处理。
    • 核心逻辑:服务端从Socket输入流读取类型编号,通过switch分支判断并执行对应逻辑。
    // 服务端接收消息类型的核心逻辑
    DataInputStream dis = new DataInputStream(socket.getInputStream());
    int type = dis.readInt(); // 读取消息类型编号
    switch (type) {case 1: // 处理登录消息break;case 2: // 处理群聊消息break;// 其他消息类型...
    }
    
  • 服务端接收登录消息的处理

    • 读取昵称:当消息类型为1时,通过输入流读取客户端发送的昵称。
    • 存储在线信息:将当前Socket和昵称存入onlineSockets集合,标记客户端上线。
    // 处理登录消息
    String nickname = dis.readUTF(); // 读取昵称
    Server.onlineSockets.put(socket, nickname); // 存入在线集合
    
  • 更新全部客户端在线人数列表的方法

    • 方法功能:向所有在线客户端推送最新的在线用户列表。
    • 实现步骤:
      1. 获取所有在线用户的昵称(onlineSocketsvalues)。
      2. 遍历所有在线Socket,通过输出流向每个客户端发送更新消息:
        • 先发送消息类型(1代表在线列表更新)。
        • 发送用户数量,再逐个发送用户名。
    // 更新在线人数列表的方法
    private void updateClientOnlineList() {// 获取所有在线用户名Collection<String> allNicknames = Server.onlineSockets.values();// 遍历所有在线Socket管道for (Socket clientSocket : Server.onlineSockets.keySet()) {try {DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());dos.writeInt(1); // 消息类型:在线列表更新dos.writeInt(allNicknames.size()); // 发送用户数量for (String nickname : allNicknames) {dos.writeUTF(nickname); // 逐个发送用户名}dos.flush(); // 刷新数据} catch (IOException e) {e.printStackTrace();}}
    }
    
  • 客户端下线的处理

    • 当客户端断开连接(抛出异常),服务端需将其SocketonlineSockets中移除,并触发在线列表更新。
    // 客户端下线时移除在线记录
    Server.onlineSockets.remove(socket);
    updateClientOnlineList(); // 重新更新在线列表
    
  • 整体流程

    1. 服务端接收消息类型编号,判断为登录消息(1)。
    2. 读取昵称并存储到onlineSockets
    3. 调用updateClientOnlineList()方法,向所有客户端推送更新后的在线列表。
    4. 客户端接收消息后,更新本地展示的在线用户列表。

五、服务端(聊天信息转发模块)

  • 解决下线操作的bug
    下线时需更新所有客户端的在线人数列表,需重新调用更新在线人数的方法。此时map集合中已移除下线客户端信息,遍历剩余socket推送更新后的列表(消息类型为1号)。

    // 下线时调用更新在线人数列表的方法
    updateOnlineUserList();// 更新在线人数列表的方法逻辑
    private void updateOnlineUserList() {// 获取当前在线用户列表(已移除下线用户)Collection<String> usernames = onlineSockets.values();// 遍历所有在线socket,推送更新后的列表for (Socket socket : onlineSockets.keySet()) {DataOutputStream dos = new DataOutputStream(socket.getOutputStream());dos.writeInt(1); // 1号消息:更新在线人数dos.writeUTF(String.join(",", usernames));dos.flush();}
    }
    
  • 接收群聊消息并转发的整体逻辑
    服务端线程接收2号类型的群聊消息后,需转发给所有在线socket(包括发送者自身),确保消息在所有客户端面板展示。

  • 读取客户端发送的文本消息
    从数据输入流中读取客户端的文本消息:

    DataInputStream dis = new DataInputStream(socket.getInputStream());
    String message = dis.readUTF(); // 读取客户端发送的群聊内容
    
  • 拼装消息内容

    1. 获取发送者昵称:通过当前socketmap集合中获取对应的用户名
      String senderName = Server.onlineSockets.get(socket); // onlineSockets为<Socket, String>类型的map
      
    2. 获取并格式化时间:使用LocalDateTimeDateTimeFormatter处理时间
      LocalDateTime now = LocalDateTime.now();
      DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
      String timeStr = dtf.format(now); // 格式化时间为字符串
      
    3. 拼接消息:使用StringBuilder组合昵称、时间和消息内容,添加格式符优化展示
      StringBuilder sb = new StringBuilder();
      sb.append(senderName) // 发送者昵称.append(" ") .append(timeStr) // 发送时间.append("\r\n") // 换行.append(message) // 消息内容.append("\r\n"); // 消息间换行
      String fullMessage = sb.toString(); // 转换为字符串
      
  • 转发消息给所有在线客户端
    遍历所有在线socket,发送拼装好的消息(消息类型为2号):

    private void sendMsgToAll(String fullMessage) {for (Socket socket : Server.onlineSockets.keySet()) {try {DataOutputStream dos = new DataOutputStream(socket.getOutputStream());dos.writeInt(2); // 2号消息:群聊消息dos.writeUTF(fullMessage);dos.flush(); // 刷新输出流} catch (IOException e) {e.printStackTrace();}}
    }
    
  • 添加死循环处理多次消息
    线程需通过死循环持续接收消息,避免只处理一次后终止:

    while (true) { // 死循环:持续监听客户端消息int msgType = dis.readInt(); // 读取消息类型if (msgType == 2) { // 处理2号群聊消息String message = dis.readUTF();sendMsgToAll(buildFullMessage(socket, message)); // 拼装并转发消息}// 可扩展处理其他消息类型(如3号私聊消息)
    }
    

六、客户端(登录开发)

  • 开发客户端的准备与思路:服务端模块已开发完成,接下来需开发客户端,客户端初始仅有界面,需与服务端对接。开发从登录界面开始,遵循用户思维和线性思维,即按照用户操作流程推进。

  • 登录界面的初始操作

    • 给登录界面的“进入”按钮(后改为“登录”按钮)绑定点击事件监听器,代码使用匿名内部类或Lambda表达式简化实现:
    entryButton.addActionListener(e -> {// 获取昵称String nickname = nicknameInput.getText();nicknameInput.setText(""); // 清空输入框if (nickname != null && !nickname.isEmpty()) {try {login(nickname); // 调用登录方法dispose(); // 关闭登录窗口} catch (IOException ex) {ex.printStackTrace();}}
    });
    
    • 点击按钮后,从输入框获取昵称,判断非空后执行登录逻辑,并关闭登录窗口。
  • 登录方法的创建与完善

    • 将登录相关代码独立为login方法,避免代码臃肿:
    private void login(String nickname) throws IOException {// 连接服务端socket = new Socket(Constant.SERVER_IP, Constant.SERVER_PORT);// 发送登录信息DataOutputStream dos = new DataOutputStream(socket.getOutputStream());dos.writeInt(1); // 消息类型为登录(1代表登录)dos.writeUTF(nickname); // 发送昵称dos.flush(); // 刷新缓冲区// 登录成功后进入聊天界面new ClientChatFrame(nickname, socket);
    }
    
    • 服务端的IP和端口在常量类Constant中定义,方便后续修改:
    public class Constant {public static final String SERVER_IP = "127.0.0.1"; // 服务器IPpublic static final int SERVER_PORT = 666; // 服务器端口,需与服务端保持一致
    }
    
  • 发送登录消息给服务端:连接成功后,通过DataOutputStream向服务端发送消息类型(1代表登录)和昵称,且不能关闭流和管道,否则会中断后续通信。

  • 进入聊天界面的准备:登录成功后,启动聊天界面(ClientChatFrame类)。需将昵称和Socket管道传给聊天界面,因此在登录界面将Socket定义为全局变量:

    private Socket socket; // 登录界面的全局Socket变量,用于保存与服务端的连接
    
  • 聊天界面的初始化

    • 聊天界面通过有参构造器接收昵称和Socket管道,并调用无参构造器初始化界面:
    public class ClientChatFrame extends JFrame {private String nickname;private Socket socket;public ClientChatFrame(String nickname, Socket socket) {this(); // 调用无参构造器初始化界面this.nickname = nickname;this.socket = socket;setTitle(nickname + "的聊天窗口"); // 在窗口标题展示昵称}public ClientChatFrame() {// 初始化界面组件的代码initComponents();}
    }
    
    • 聊天界面将昵称展示在窗口标题上,方便用户识别当前登录账号,同时保存Socket管道用于后续接收在线人数列表、发送和接收消息等操作。

七、客户端(在线人数展示)

  • 回顾登录界面跳转逻辑:登录成功后,将昵称和Socket管道传递给聊天界面,并销毁登录窗口,避免资源占用。关键代码逻辑如下(示意):
// 登录成功后跳转至聊天界面
ChatFrame chatFrame = new ChatFrame(nickname, socket);
chatFrame.setVisible(true);
this.dispose(); // 销毁当前登录窗口
  • 明确客户端核心任务:登录后需实时读取服务端发送的两类消息:

    • 在线人数更新消息(类型1)
    • 群聊消息(类型2)
  • 采用多线程处理消息收发

    • 独立线程(ClientReaderThread)负责持续接收服务端消息,避免阻塞主线程
    • 主线程负责处理用户交互(如发送消息)
  • 创建客户端消息读取线程类

public class ClientReaderThread extends Thread {private Socket socket;private ChatFrame chatFrame; // 持有聊天界面对象public ClientReaderThread(Socket socket, ChatFrame chatFrame) {this.socket = socket;this.chatFrame = chatFrame;}@Overridepublic void run() {try (DataInputStream dis = new DataInputStream(socket.getInputStream())) {while (true) {int type = dis.readInt(); // 读取消息类型if (type == 1) {// 处理在线人数更新updateClientOnlineUserList(dis);} else if (type == 2) {// 处理群聊消息(下节课实现)}}} catch (IOException e) {e.printStackTrace();}}
}
  • 在线人数更新方法实现
private void updateClientOnlineUserList(DataInputStream dis) throws IOException {int count = dis.readInt(); // 读取在线用户数量String[] onlineUsers = new String[count];// 循环读取所有在线用户名for (int i = 0; i < count; i++) {onlineUsers[i] = dis.readUTF();}// 调用聊天界面方法更新UIchatFrame.updateOnlineUsers(onlineUsers);
}
  • 聊天界面更新UI组件
public class ChatFrame extends JFrame {private JList<String> onlineUserList; // 展示在线用户的列表组件// 更新在线用户列表public void updateOnlineUsers(String[] users) {DefaultListModel<String> model = new DefaultListModel<>();for (String user : users) {model.addElement(user);}onlineUserList.setModel(model);}
}
  • 线程启动与数据传递:在聊天界面初始化时启动读取线程,并传递Socket和界面对象:
// 聊天界面构造方法中启动读取线程
public ChatFrame(String nickname, Socket socket) {this.nickname = nickname;this.socket = socket;// 启动消息读取线程new ClientReaderThread(socket, this).start();
}
  • 功能测试验证
    • 启动服务端后,多客户端登录测试在线人数同步展示
    • 关闭任一客户端,验证其他客户端在线列表实时移除该用户

八、客户端(群聊功能)

  • 接收群聊消息逻辑

    • 接收消息类型为2的群聊消息,通过输入流读取服务端发送的UTF格式消息(包含发送者、时间等信息)。
    • 将消息展示到界面面板,核心代码如下:
    // 读取群聊消息
    String message = dis.readUTF();
    // 将消息更新到窗口
    win.setMessageToWindow(message);
    
    • 在窗口类中实现setMessageToWindow方法,将消息追加到展示区域:
    public void setMessageToWindow(String message) {msgArea.append(message);
    }
    
  • 发送群聊消息功能

    • 为发送按钮绑定点击事件,获取输入框内容并清空,通过输出流向服务端发送消息。
    • 先发送消息类型2,再发送具体的群聊内容,核心代码如下:
    // 为发送按钮绑定点击事件
    sendButton.addActionListener(e -> {String message = inputField.getText();inputField.setText(""); // 清空输入框sendMessageToServer(message);
    });// 发送消息到服务端
    private void sendMessageToServer(String message) {try (DataOutputStream dos = new DataOutputStream(socket.getOutputStream())) {dos.writeInt(2); // 发送群聊消息类型dos.writeUTF(message); // 发送消息内容dos.flush();} catch (IOException e) {e.printStackTrace();}
    }
    
  • 功能测试

    • 启动服务端和多个客户端,测试不同用户登录后发送消息的接收情况。
    • 例如,用户“张三”发送“我好慌哦”,用户“王麻子”发送“麻子你在干啥”,验证所有客户端能否正常接收消息。
    • 测试中发现换行显示存在小问题,但不影响核心功能使用。
  • 多人测试与优化方向

    • 修改客户端连接的IP地址(如192.168.25.70),连接到同一服务端进行多人测试。
    • 可优化的方向包括:消息自动滚动到底部、完善换行显示、支持发送图片、美化界面等。
GitHub:https://github.com/Andy123211/chat-system/tree/master

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

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

相关文章

linux 的list_for_each_entry

linux的宏定义提高了代码的简洁性&#xff0c;但有时候的命名不够完美。比如list_for_each_entry&#xff0c;看名字只知道是遍历list&#xff0c;但一看里面的三个变量参数&#xff0c;有点懵逼。/*** list_for_each_entry - iterate over list of given type* pos: …

分布式面试点

目录 1.分布式理论 为什么CAP不可兼得呢? 2.CAP对应的模型和应用 3.Base理论 4,有哪些分布式锁的案例 5.分布式事务 6.Seata 分布式一致性算法 1. 准备阶段&#xff08;Prepare Phase&#xff09; 2. 接受阶段&#xff08;Accept Phase&#xff09; 3. 学习阶段&…

Neo4j系列---【Linux离线安装neo4j】

Linux离线安装neo4j 1.官方安装文档 地址&#xff1a;https://neo4j.com/docs/operations-manual/current/installation/linux/tarball/ 2.如果浏览器无法访问 修改neo4j.conf,开放所有ip访问 # 允许所有IP地址访问 server.default_listen_address0.0.0.0 3.创建开机自启动服务…

SEO长尾关键词核心实战技巧提升排名

内容概要 本文聚焦于SEO长尾关键词的核心实战技巧&#xff0c;旨在帮助读者精准锁定目标用户的搜索意图&#xff0c;从而提升网站自然排名和获取精准流量。文章将从基础概念入手&#xff0c;系统解析如何挖掘高转化率的长尾关键词&#xff0c;优化内容结构以增强搜索可见度&…

当OT遇见IT:Apache IoTDB如何用“时序空间一体化“技术破解工业物联网数据孤岛困局?

目录 一. 什么是时序数据库&#xff1f; 二. 时序数据库的选型要素 性能指标 架构能力 数据模型与查询能力 安全与权限控制 部署与运维能力 三 Apache IoTDB 简介及安装使用&#xff1a; 安装准备教程 检查 Java 版本 下载与安装 下载 IoTDB 解压文件 配置环境变量 启动…

一文讲透HTML语义化标签

文章目录语义化标签概述HTML标签及其含义常见HTML5语义化标签语义化标签对搜索引擎&#xff08;SEO&#xff09;的影响提升搜索引擎排名增强可访问性改善用户体验语义化标签案例各标签作用说明语义化标签概述 HTML 语义化是指使用恰当的标签来准确表达内容的结构和含义&#x…

Django 实战:静态文件与媒体文件从开发配置到生产部署

文章目录一、静态文件与媒体文件区别与联系配置开发环境配置二、媒体文件实战实战场景定义模型定义序列化器定义视图实战效果三、生产部署说明收集静态文件Nginx配置示例OpenResty配置示例一、静态文件与媒体文件 区别与联系 在 Django 项目中&#xff0c;静态文件&#xff0…

Python自动化分析知网文献:爬取、存储与可视化

1. 引言 在当今的学术研究和大数据分析领域&#xff0c;高效获取和分析学术文献数据具有重要意义。中国知网&#xff08;CNKI&#xff09;作为国内最权威的学术资源平台之一&#xff0c;包含了海量的期刊论文、会议论文和学位论文。然而&#xff0c;手动收集和分析这些数据不仅…

Python应用指南:使用PyKrige包实现ArcGIS的克里金插值法

先了解什么是克里金插值&#xff1f;克里金插值&#xff08;Kriging interpolation&#xff09;是一种基于统计学和空间相关性的高级空间插值方法&#xff0c;广泛应用于地理信息系统&#xff08;GIS&#xff09;、地质勘探、环境科学、气象学等领域。它由南非矿业工程师丹尼尔…

Redis原理之哨兵机制(Sentinel)

上篇文章&#xff1a; Redis原理之主从复制https://blog.csdn.net/sniper_fandc/article/details/149141103?fromshareblogdetail&sharetypeblogdetail&sharerId149141103&sharereferPC&sharesourcesniper_fandc&sharefromfrom_link 目录 1 哨兵机制恢…

uniapp打包成 apk

1. 先把项目打包成 index.html 上传到宝塔服务器,关联到域名 2.然后再用hbuilder新建一个 基础模板的 uniapp 3.再修改代码,采用iframe方式打包 pages/index/index <template><web-view v-if="showWebView" :src="webViewSrc" @message=&qu…

RPG57.创建玩家拾取物品类一:创建可拾取物品类的基类

1。新建一个基类&#xff0c;用于玩家可拾取物品的父类然后// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h" #include "Components/SphereComponent.h" #include "GameFramewo…

k8s之持久化存储流程

K8s 中的 Pod 在挂载存储卷时需经历三个的阶段&#xff1a;Provision/Delete&#xff08;创盘/删盘&#xff09;、Attach/Detach&#xff08;挂接/摘除&#xff09;和 Mount/Unmount&#xff08;挂载/卸载&#xff09; Provisioning Volumes 时序流程详解 一、流程图 sequenc…

python学智能算法(二十四)|SVM-最优化几何距离的理解

引言 前序学习过程中&#xff0c;已经对几何距离的概念有了认知&#xff0c;学习链接为&#xff1a;几何距离 这里先来回忆几何距离δ的定义&#xff1a; δmin⁡i1...myi(w∥w∥⋅xib∥w∥)\delta \min_{i1...m}y_{i}(\frac{w}{\left \| w \right \|}\cdot x_{i}\frac{b}{\le…

创建游戏或互动体验:从概念到实现的完整指南

Hi&#xff0c;我是布兰妮甜 &#xff01;在数字时代&#xff0c;游戏和互动体验已成为娱乐、教育和商业领域的重要组成部分。本文将带你了解如何使用JavaScript创建引人入胜的游戏和互动体验&#xff0c;从基础概念到实际实现。 文章目录一、游戏开发基础1.1 游戏循环1.2 游戏…

SpringMVC + Tomcat10

1. Tomcat 10的servlet包路径变了&#xff0c;javax -> jakarta 2. DispatcherServlet从Spring6 才开始使用jakarta.servlet.http.Servlet 3. Spring6 需要JDK 17 1. pom <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org…

Django `transaction.atomic()` 完整使用指南

目录 #概述#基本用法#事务一致性保障机制#破坏一致性的常见场景#高级用法#最佳实践#诊断与调试#附录 概述 transaction.atomic() 是 Django 提供的数据库事务管理工具&#xff0c;用于确保一系列数据库操作要么全部成功提交&#xff0c;要么全部回滚&#xff0c;维护数据的一致…

UDP协议的端口161怎么检测连通性

UDP 端口 161 (SNMP) 连通性检测的专业指南 UDP 161 端口是 SNMP (Simple Network Management Protocol) 服务的标准端口。由于其无连接特性&#xff0c;检测需要特殊方法。以下是全面的检测方案&#xff1a; 一、专业检测方法 1. 使用 SNMP 专用工具&#xff08;推荐&#xff…

进阶数据结构:红黑树

嘿&#xff0c;各位技术潮人&#xff01;好久不见甚是想念。生活就像一场奇妙冒险&#xff0c;而编程就是那把超酷的万能钥匙。此刻&#xff0c;阳光洒在键盘上&#xff0c;灵感在指尖跳跃&#xff0c;让我们抛开一切束缚&#xff0c;给平淡日子加点料&#xff0c;注入满满的pa…

如何上传github(解决git的时候输入正确的账号密码,但提示认证失败)

如何上传github文件&#xff0c;删除文件 1.重点 GitHub 从 2021 年 8 月 13 日起移除了对密码认证的支持。你需要使用个人访问令牌(Personal Access Token, PAT)或 SSH 密钥来进行认证。 2.生成SSH key 进入设置点击New SSH Key名字随便取&#xff0c;可以自己方便记3.上传文件…