SpringBoot实现websocket服务端及客户端的详细过程

本文主要是介绍SpringBoot实现websocket服务端及客户端的详细过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《SpringBoot实现websocket服务端及客户端的详细过程》文章介绍了WebSocket通信过程、服务端和客户端的实现,以及可能遇到的问题及解决方案,感兴趣的朋友一起看看吧...

一、WebSocket通信过程

客户端构建一个websocket实例,并且为它绑定一个需要连接到的服务器地址,当客户端连接服务端的候,会向服务端发送一个http get报文,告诉服务端需要将通信协议切换到websocket,服务端收到http请求后将通信协议切换到websocket,同时发给客户端一个响应报文,返回的状态码为101,表示同意客户端协议转请求,并转换为websocket协议。以上过程都是利用http通信完成的,称之为websocket协议握手(websocket Protocol handshake),经过握手之后,客户端和服务端就建立了websocket连接,以后的通信走的都是websocket协议了。

二、服务端实现

1.pom文件添加依赖

       <!--webSocket-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

2.启用Springboot对WebSocket的支持

package com.lby.websocket.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
 * @author Liby
 * @date 2022-04-25 16:18
 * @description:
 * @version:
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
 

3.核心配置:WebSocketServer

因为WebSocket是类似客户端服务端的形式(采用ws协议),那么这里的WebSocketServer其实就相当于一个ws协议的Controller

@ ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端, 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
新建一个ConcurrentHashMap webSocketMap 用于接收当前userId的WebSocket,方便传递之间对userId进行推送消息。
下面是具体业务代码:

package org.example.server;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;
import Javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
/*
启动项目后可通过 http://websocket.jsonin.com/ 进行测试
输入网址ws://localhost:8080/China编程websocket/1211,检测是否能进行连接
 */
@ServerEndpoint(value = "/websocket/{userId}")
@Component
public class WebSocket {
    private final static Logger logger = LogManager.getLogger(WebSocket.class);
    /**
     * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的
     */
    private static int onlineCount = 0;
    /**
     * concurrent包的线程安全Map,用来存放每个客户端对应的MyWebSocket对象
     */
    private static ConcurrentHashMap<String, WebSocket> webSocketMap = new ConcurrentHashMap<>();
    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    private Session session;
    private String userId;
    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        this.session = session;
        this.userId = userId;
        //加入map
        webSocketMap.put(userId, this);
        addOnlineCount();           //在线数加1
        logger.info("用户{}连接成功,当前在线人数为{}", userId, getOnlineCounTkRlot());
        try {
            sendMessage(String.valueOf(this.session.getQueryString()));
        } catch (IOException e) {
            logger.error("IO异常");
        }
    }
    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        //从map中删除
        webSocketMap.remove(userId);
        subOnlineCount();           //在线数减1
        logger.info("用户{}关闭连接!当前在线人数为{}", userId, getOnlineCount());
    }
    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        logger.info("来自客户端用户:{} 消息:{}",userId, message);
        //群发消息
        for (String item : webSocketMap.keySet()) {
            try {
                webSocketMap.get(item).sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 发生错误时调用
     *
     */
    @OnError
    public void onError(Session session, Throwable error) {
        logger.error("用户错误:" + this.userId + ",原因:" + error.getMessage());
        error.printStackTrace();
    }
    /**
     * 向客户端发送消息
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
        //this.session.getAsyncRemote().sendText(message);
    }
    /**
     * 通过userId向客户端发送消息
     */
    public void sendMessageByUserId(String userId, String message) throws IOException {
        logger.info("服务端发送消息到{},消息:{}",userId,message);
        if(StrUtil.isNotBlank(userId)&&webSocketMap.containsKey(userId)){
            webSocketMap.get(userId).sendMessage("hello");
        }else{
            logger.error("用户{}不在线",userId);
        }
    }
    /**
     * 群发自定义消息
     */
    public void sendInfo(String message) throws IOException {
        for (String item : webSopythoncketMap.keySet()) {
            try {
                webSocketMap.get(item).sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }
    public static synchronized void addOnlineCount() {
        WebSocket.onlineCount++;
    }
    public static synchronized void subOnlineCount() {
        WebSocket.onlineCount--;
    }
}
 

三、客户端实现

1.pom文件添加依赖

        <dependency>
            <groupId>org.java-websocket</groupId>
            <artifactId>Java-WebSocket</artifactId>
            <version>1.5.3</version>
        </dependency>

2.客户端实现代码

package org.example;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
 * @author kele
 * @date 2024/2/19
 **/
@Slf4j
public class MyWebSocketClient extends WebSocketClient {
    public MyWebSocketClient(URI serverUri) {
        super(serverUri);
    }
    @SneakyThrows
    @Override
    public void onOpen(ServerHandshake data) {
        try {
            log.info("WebSocket连接已打开。");
        }catch (Exception e){
            log.error("onOpen error :{}",e.getMessage());
        }
    }
    @SneakyThrows
    @Override
    public void onMessage(String message) {
        try {
            if (message != null && !message.isEmpty()) {
                log.info("收到消息: {}",message);
            }
        }catch (Exception e){
            log.error("onMessage error : {}",message);
        }
    }
    @Override
    public void onClose(int code, String reason, boolean remote) {
        log.info("WebSocket连接已关闭。");
    }
    @Override
    public void onError(Exception ex) {
        log.info("WebSocket连接发生错误:{}", exhttp://www.chinasem.cn.getMessage());
    }
    /**
     * 连接定时检查
     */
    public void startReconnectTask(long delay, TimeUnit unit) {
        System.out.println("WebSocket 心跳检查");
        log.info("WebSocket 心跳检查");
        // 以下为定时器,建议使用自定义线程池,或交给框架处理(spring)
        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledEhttp://www.chinasem.cnxecutor();
        executorService.scheduleWithFixedDelay(() -> {
            // 检查逻辑:判断当前连接是否连通
            if (!this.isOpen()) {
                System.out.println("WebSocket 开始重连......");
                log.info("WebSocket 开始重连......");
                // 重置连接
                this.reconnect();
                // 以下为错误示范
                //this.close();
                //this.connect();
            }
        }, 0, delay, unit);
    }
}

3.开启客户端连接服务,并开启定时检查websocket连接

package org.example;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.net.URI;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class Init implements Runnable {
    public static MyWebSocketClient myWebSocketClient;
    @PostConstruct
    public void run () {
        try {
            //启动连接
            log.info("连接websocket服务端");
            log.info("项目启动");
            // 服务地址
            URI uri = new URI("ws://127.0.0.1:8077/websocket/123");
            log.info("服务地址 -{}", uri);
            // 创建客户端
            myWebSocketClient = new MyWebSocketClient(uri);
            // 建立连接
            myWebSocketClient.connect();
            // 开启 定时检查
            myWebSocketClient.startReconnectTask(5, TimeUnit.SECONDS);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

四、可能遇到的问题及解决方案

1.websocket客户端重连机制

注意引入Java-websocket的版本要高于1.5.0才有reconnect()方法

SpringBoot实现websocket服务端及客户端的详细过程

2.在使用websocket连接传输Base64编码时出现错误

原因:Websocket中maxMessageSize默认是8K,接收的图片大于8K导致接收失败,解决方法:适当加大maxMessageSize。

参考资料:

注意不能设置太大,避免内存溢出。参考博客1,博客2,博客3

@OnMessage(maxMessageSize = 10240000)
public void onMessage(byte[] message) throws IOException {
    log.info("收到图片了");
    String str = Base64.encodeBase64String(message); // 图片base64
    log.info(str.substring(0, 100));
}

到此这篇关于SpringBoot实现websocket服务端及客户端的文章就介绍到这了,更多相关SpringBoot websocket服务端及客户端内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!

这篇关于SpringBoot实现websocket服务端及客户端的详细过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1152863

相关文章

Nginx实现高并发的项目实践

《Nginx实现高并发的项目实践》本文主要介绍了Nginx实现高并发的项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录使用最新稳定版本的Nginx合理配置工作进程(workers)配置工作进程连接数(worker_co

python中列表list切分的实现

《python中列表list切分的实现》列表是Python中最常用的数据结构之一,经常需要对列表进行切分操作,本文主要介绍了python中列表list切分的实现,文中通过示例代码介绍的非常详细,对大家... 目录一、列表切片的基本用法1.1 基本切片操作1.2 切片的负索引1.3 切片的省略二、列表切分的高

基于Python实现一个PDF特殊字体提取工具

《基于Python实现一个PDF特殊字体提取工具》在PDF文档处理场景中,我们常常需要针对特定格式的文本内容进行提取分析,本文介绍的PDF特殊字体提取器是一款基于Python开发的桌面应用程序感兴趣的... 目录一、应用背景与功能概述二、技术架构与核心组件2.1 技术选型2.2 系统架构三、核心功能实现解析

使用Java发送邮件到QQ邮箱的完整指南

《使用Java发送邮件到QQ邮箱的完整指南》在现代软件开发中,邮件发送功能是一个常见的需求,无论是用户注册验证、密码重置,还是系统通知,邮件都是一种重要的通信方式,本文将详细介绍如何使用Java编写程... 目录引言1. 准备工作1.1 获取QQ邮箱的SMTP授权码1.2 添加JavaMail依赖2. 实现

Java嵌套for循环优化方案分享

《Java嵌套for循环优化方案分享》介绍了Java中嵌套for循环的优化方法,包括减少循环次数、合并循环、使用更高效的数据结构、并行处理、预处理和缓存、算法优化、尽量减少对象创建以及本地变量优化,通... 目录Java 嵌套 for 循环优化方案1. 减少循环次数2. 合并循环3. 使用更高效的数据结构4

使用Python实现表格字段智能去重

《使用Python实现表格字段智能去重》在数据分析和处理过程中,数据清洗是一个至关重要的步骤,其中字段去重是一个常见且关键的任务,下面我们看看如何使用Python进行表格字段智能去重吧... 目录一、引言二、数据重复问题的常见场景与影响三、python在数据清洗中的优势四、基于Python的表格字段智能去重

java两个List的交集,并集方式

《java两个List的交集,并集方式》文章主要介绍了Java中两个List的交集和并集的处理方法,推荐使用Apache的CollectionUtils工具类,因为它简单且不会改变原有集合,同时,文章... 目录Java两个List的交集,并集方法一方法二方法三总结java两个List的交集,并集方法一

Spring AI集成DeepSeek三步搞定Java智能应用的详细过程

《SpringAI集成DeepSeek三步搞定Java智能应用的详细过程》本文介绍了如何使用SpringAI集成DeepSeek,一个国内顶尖的多模态大模型,SpringAI提供了一套统一的接口,简... 目录DeepSeek 介绍Spring AI 是什么?Spring AI 的主要功能包括1、环境准备2

Spring AI集成DeepSeek实现流式输出的操作方法

《SpringAI集成DeepSeek实现流式输出的操作方法》本文介绍了如何在SpringBoot中使用Sse(Server-SentEvents)技术实现流式输出,后端使用SpringMVC中的S... 目录一、后端代码二、前端代码三、运行项目小天有话说题外话参考资料前面一篇文章我们实现了《Spring

Spring AI与DeepSeek实战一之快速打造智能对话应用

《SpringAI与DeepSeek实战一之快速打造智能对话应用》本文详细介绍了如何通过SpringAI框架集成DeepSeek大模型,实现普通对话和流式对话功能,步骤包括申请API-KEY、项目搭... 目录一、概述二、申请DeepSeek的API-KEY三、项目搭建3.1. 开发环境要求3.2. mav