WebSocket(java版)服务示例

WebSocket(java版)服务示例

说明:

这是一个使用 Java JDK 8 和 Spring Boot 实现的WebSocket演示项目。目的是为解决多端消息通讯的问题。

WebSocket 是一种基于 TCP 的全双工通信协议,核心作用是解决传统 HTTP 协议 “请求 - 响应” 模式的局限性,实现 客户端与服务器之间的实时、双向、低延迟数据传输

源码地址:https://gitee.com/lqh4188/web-socket

一、功能介绍

功能特性

  • 基于 Maven 的 Spring Boot 项目骨架。
  • 纯 WebSocket 端点 /ws ,支持用户隔离,http:使用ws,https:使用wss。
  • 支持分片设置和缓冲区大小设置,解决传输内容限制
  • 提供静态测试页面 index.html ,用于连接、发送消息、查看消息。

项目结构:

  • pom.xml :Spring Boot 3.3,依赖 spring-boot-starter-web 和 spring-boot-starter-websocket 。
  • src/main/java/com/example/websocket/WebSocketApplication.java :应用入口。
  • src/main/java/com/example/websocket/WebSocketConfig.java :注册 WebSocket 处理器,端点为 /ws 。
  • src/main/java/com/example/websocket/ChatWebSocketHandler.java :文本消息处理,广播到所有会话。
  • src/main/resources/static/index.html :页面内置 JS,连接 ws://{host}/ws ,可发送、显示消息。

关键代码位置

  • 启动类: src/main/java/com/example/websocket/WebSocketApplication.java:1
  • WebSocket 配置: src/main/java/com/example/websocket/WebSocketConfig.java:1
  • 文本消息处理器: src/main/java/com/example/websocket/ChatWebSocketHandler.java:1
  • 静态页面: src/main/resources/static/index.html:1

测试连接

二、运行测试

可通过UserId来创建独立的联接,进行用户隔离

三、核心代码说明

由于websocket对传输的内容有限制,若内容较大可进行缓冲区大小设置,并对不同文本进行分片处理

ChatWebSocketHandler.java代码:

package com.example.websocket; import java.io.ByteArrayOutputStream; import java.net.URI; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.springframework.web.socket.BinaryMessage; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.AbstractWebSocketHandler; import com.fasterxml.jackson.databind.ObjectMapper; public class ChatWebSocketHandler extends AbstractWebSocketHandler { private final ConcurrentHashMap<String, Set<WebSocketSession>> userSessions = new ConcurrentHashMap<>(); private static final ObjectMapper MAPPER = new ObjectMapper(); private final ConcurrentHashMap<String, StringBuilder> textFragments = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, ByteArrayOutputStream> binaryFragments = new ConcurrentHashMap<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { // 验证用户ID的有效性 String uid = resolveUserId(session); if (uid == null || uid.isEmpty()) { session.close(CloseStatus.BAD_DATA); return; } session.getAttributes().put("userId", uid); //多会话管理 userSessions.computeIfAbsent(uid, k -> ConcurrentHashMap.newKeySet()).add(session); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { // 分片处理 String id = session.getId(); if (!message.isLast()) { textFragments.computeIfAbsent(id, k -> new StringBuilder()).append(message.getPayload()); return; } StringBuilder sb = textFragments.remove(id); String payload = sb != null ? sb.append(message.getPayload()).toString() : message.getPayload(); routePayload(session, payload); } @Override protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception { //二进制消息处理 String id = session.getId(); ByteBuffer buf = message.getPayload(); byte[] chunk = new byte[buf.remaining()]; buf.get(chunk); ByteArrayOutputStream acc = binaryFragments.computeIfAbsent(id, k -> new ByteArrayOutputStream()); acc.write(chunk); if (message.isLast()) { byte[] all = acc.toByteArray(); binaryFragments.remove(id); String payload = new String(all, StandardCharsets.UTF_8); routePayload(session, payload); } } @Override public boolean supportsPartialMessages() { return true; } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { // WebSocket 连接关闭时的清理逻辑 Object v = session.getAttributes().get("userId"); if (v == null) return; String uid = String.valueOf(v); Set<WebSocketSession> set = userSessions.get(uid); if (set != null) { set.remove(session); if (set.isEmpty()) userSessions.remove(uid); } } /** 从 WebSocket 连接的 URL 查询参数中提取用户ID */ private String resolveUserId(WebSocketSession session) { URI uri = session.getUri(); if (uri == null) return null; String q = uri.getQuery(); if (q == null || q.isEmpty()) return null; String[] parts = q.split("&"); for (String p : parts) { int i = p.indexOf('='); if (i > 0) { String k = p.substring(0, i); String val = p.substring(i + 1); if ("userId".equals(k)) return val; } } return null; } private void routePayload(WebSocketSession session, String payload) throws Exception { Object v = session.getAttributes().get("userId"); if (v == null) return; String fromUid = String.valueOf(v); // 解析消息 Message message = new Message(); message.setFromUserId(fromUid); try { // 尝试将payload解析为Message对象 Message receivedMsg = MAPPER.readValue(payload, Message.class); message.setToUserId(receivedMsg.getToUserId()); message.setContent(receivedMsg.getContent()); message.setType(receivedMsg.getType()); } catch (Exception e) { // 如果解析失败,将整个payload作为content message.setContent(payload); } String toUid = message.getToUserId(); boolean isP2P = toUid != null && !toUid.isEmpty(); Set<WebSocketSession> targets; if (isP2P) { targets = userSessions.get(toUid); } else { targets = userSessions.get(fromUid); } // 序列化消息对象 String outStr = MAPPER.writeValueAsString(message); TextMessage msg = new TextMessage(outStr); if (targets == null || targets.isEmpty()) { if (session.isOpen()) { session.sendMessage(msg); } return; } for (WebSocketSession s : targets) { if (s.isOpen()) { s.sendMessage(msg); } } if (isP2P && session.isOpen()) { session.sendMessage(msg); } } } 

配置类WebSocketConfig.java

package com.example.websocket; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean; @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(chatHandler(), "/ws").setAllowedOriginPatterns("*"); } @Bean public WebSocketHandler chatHandler() { return new ChatWebSocketHandler(); } // 配置 WebSocket 容器参数(解决消息过大、超时等问题) @Bean public ServletServerContainerFactoryBean createWebSocketContainer() { ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); // 文本消息缓冲区:2MB(解决解码后消息过大的核心配置) container.setMaxTextMessageBufferSize(2 * 1024 * 1024); // 二进制消息缓冲区:4MB(按需配置) container.setMaxBinaryMessageBufferSize(4 * 1024 * 1024); // 会话空闲超时:60秒(无交互则关闭连接) container.setMaxSessionIdleTimeout(60_000L); return container; } } 

Read more

B树与B+树:从原理到实现的深度解析

第1章:引言:为什么需要多路平衡查找树? 1.1 计算机存储层次结构带来的挑战 在现代计算机系统中,存储层次结构从寄存器、高速缓存、主内存到磁盘,访问速度差异巨大。以典型的现代计算机为例: * CPU寄存器:访问延迟约0.3纳秒 * L1高速缓存:访问延迟约0.9纳秒 * L2高速缓存:访问延迟约2.8纳秒 * 主内存:访问延迟约12纳秒 * 固态硬盘:访问延迟约25微秒(25,000纳秒) * 机械硬盘:访问延迟约10毫秒(10,000,000纳秒) 这种访问延迟的指数级差异催生了局部性原理的优化思想。然而,当数据规模达到千万甚至亿级时,传统的二叉树结构面临严重挑战: text 二叉查找树的最坏情况: [1] \ [2] \ [3] \ ... \ [1000000] 1.2 二叉树在外部存储中的局限性 考虑一个包含1亿条记录的数据库,假设每个记录需要100字节存储空间。如果使用二叉查找树: * 平均树高约为log₂(100,

By Ne0inhk
【算法通关指南:数据结构与算法篇 】二叉树相关算法题:1.新二叉树 2.二叉树的遍历

【算法通关指南:数据结构与算法篇 】二叉树相关算法题:1.新二叉树 2.二叉树的遍历

🔥小龙报:个人主页 🎬作者简介:C++研发,嵌入式,机器人方向学习者 ❄️个人专栏:《算法通关指南》 ✨ 永远相信美好的事情即将发生 文章目录 * 前言 * 二、新二叉树 * 2.1题目 * 2.2 算法原理 * 2.3代码 * 三、 二叉树的遍历 * 3.1题目 * 3.2 算法原理 * 3.3代码 * 总结与每日励志 前言 本专栏聚焦算法题实战,系统讲解算法模块:以《c++编程》,《数据结构和算法》《基础算法》《算法实战》 等几个板块以题带点,讲解思路与代码实现,帮助大家快速提升代码能力ps:本章节题目分两部分,比较基础笔者只附上代码供大家参考,其他的笔者会附上自己的思考和讲解,希望和大家一起努力见证自己的算法成长 二、新二叉树 2.

By Ne0inhk
【强化学习】区分理解: 时序差分(TD)、蒙特卡洛(MC)、动态规划(DP)

【强化学习】区分理解: 时序差分(TD)、蒙特卡洛(MC)、动态规划(DP)

📢本篇文章是博主强化学习(RL)领域学习时,用于个人学习、研究或者欣赏使用,并基于博主对相关等领域的一些理解而记录的学习摘录和笔记,若有不当和侵权之处,指出后将会立即改正,还望谅解。文章分类在👉强化学习专栏:        【强化学习】- 【强化学习进阶】(3)---《 区分理解: 时序差分(TD)、蒙特卡洛(MC)、动态规划(DP)》 区分理解: 时序差分(TD)、蒙特卡洛(MC)、动态规划(DP) 目录 一、前言 二、时序差分(Temporal-Difference,TD) 1. 背景 2. TD方法的核心思想 3. TD与其他方法的对比 4. 常见的TD算法 三、 蒙特卡洛(Monte Carlo, MC)

By Ne0inhk
Flutter 三方库 matrix 鸿蒙终端底层复杂超维数学算力适配突破:无缝植入极限级张量系统与密集线性代数矩阵运算推演算法,解锁端侧图形处理边界-适配鸿蒙 HarmonyOS ohos

Flutter 三方库 matrix 鸿蒙终端底层复杂超维数学算力适配突破:无缝植入极限级张量系统与密集线性代数矩阵运算推演算法,解锁端侧图形处理边界-适配鸿蒙 HarmonyOS ohos

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 matrix 鸿蒙终端底层复杂超维数学算力适配突破:无缝植入极限级张量系统与密集线性代数矩阵运算推演算法,全面解锁端侧图形视觉处理边界并拔高数据分析算力上限 在图形学渲染、物理引擎模拟、复杂地理坐标转换以及端侧小型机器学习框架中,底层的矩阵运算(Matrix Operations)是决速步骤。matrix 库是一个专注于高性能线性代数计算的 Dart 库。本文将详解该库在 OpenHarmony 环境下的适配与实战应用。 封面 前言 什么是 matrix?它为 Dart 提供了一套类似于 NumPy 的多维数组运算接口。在鸿蒙操作系统这种强调极致流畅度和复杂视觉动效的系统中,利用高效的矩阵算法可以显著提升自定义 Canvas 绘图或实时传器数据处理的性能,避免因 Dart 层的低效循环导致的 UI 掉帧。 一、原理解析 1.1 基础概念 matrix 库核心基于

By Ne0inhk