WebSocket(java版)服务核心实例代码

 更新时间:2025年12月21日 10:57:53   作者:深海蓝山的博客  
WebSocket是一种协议,用于在Web应用程序和服务器之间建立实时、双向的通信连接,这篇文章主要介绍了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

测试连接

  • 打开 http://localhost:8800 ,使用页面上的“连接/发送”测试
  • WebSocket 地址: ws://localhost:8080/ws

二、运行测试

可通过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;
    }
}

总结 

到此这篇关于WebSocket(java版)服务核心代码的文章就介绍到这了,更多相关java WebSocket服务内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java获取本机IP地址的方法代码示例(内网、公网)

    Java获取本机IP地址的方法代码示例(内网、公网)

    在IT领域获取本机IP地址是一项基础但重要的任务,特别是在网络编程、远程协作和设备通信中,这篇文章主要给大家介绍了关于Java获取本机IP地址的方法(内网、公网),需要的朋友可以参考下
    2024-07-07
  • 聊聊SpringBoot整合Nacos自动刷新配置的问题

    聊聊SpringBoot整合Nacos自动刷新配置的问题

    Nacos作为SpringBoot服务的注册中心和配置中心,本例将在配置文件中配置一个 cml.age=100 的配置项,程序中编写一个方法读取配置文件,并通过 Get--->/test/age 接口提供给浏览器访问,感兴趣的朋友跟随小编一起看看吧
    2022-01-01
  • SpringBoot Shiro授权实现过程解析

    SpringBoot Shiro授权实现过程解析

    这篇文章主要介绍了SpringBoot Shiro授权实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • Java GUI进阶之流式布局管理器FlowLayout专项精讲

    Java GUI进阶之流式布局管理器FlowLayout专项精讲

    FlowLayout-流式布局管理器,按水平方向依次排列放置组件,排满一行,换下一行继续排列。排列方向(左到右 或 右到左)取决于容器的componentOrientation属性
    2022-04-04
  • SpringBoot中接口安全的5种访问控制方法详解

    SpringBoot中接口安全的5种访问控制方法详解

    在项目开发种,API接口安全已成为系统设计中不可忽视的关键环节,本文将介绍SpringBoot应用中实现接口安全的5种访问控制方法,有需要的小伙伴可以了解下
    2025-05-05
  • java中继承测试代码分析

    java中继承测试代码分析

    这篇文章主要介绍了java中继承测试代码分析,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • SpringBoot使用@Cacheable时设置部分缓存的过期时间方式

    SpringBoot使用@Cacheable时设置部分缓存的过期时间方式

    这篇文章主要介绍了SpringBoot使用@Cacheable时设置部分缓存的过期时间方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • SpringBoot中默认缓存实现方案的示例代码

    SpringBoot中默认缓存实现方案的示例代码

    这篇文章主要介绍了SpringBoot中默认缓存实现方案,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • Spring AI Alibaba + Ollama 实战教程(基于本地 Qwen3 的 Spring Boot 大模型应用)

    Spring AI Alibaba + Ollama 实战教程(基于本地 Qwen3 的 Spring 

    本文介绍了如何使用SpringAI和SpringAIAlibaba将Java/SpringBoot应用与阿里云通义(Qwen)和灵积平台(DashScope)等模型服务无缝对接,实现了在本地开发调试和云端生产环境的无缝切换,本文介绍的非常详细,感兴趣的朋友一起看看吧
    2025-12-12
  • Java中常用判空与判等方法详解

    Java中常用判空与判等方法详解

    java中感觉判空的一些方法太多了,感觉有点儿乱糟糟的,所以这篇文中就为大家简单总结一下在项目中常用的一些方法吧,希望对大家有所帮助
    2025-03-03

最新评论