当前位置:首页 > Java技术 > SpringBoot + WebSocket 开发笔记

SpringBoot + WebSocket 开发笔记

2022年08月04日 17:35:53Java技术4

1. 服务端的实现,我尝试了两种方式:

  • 第一种是用“@ServerEndPoint”注解来实现,实现简单;
  • 第二种稍显麻烦,但是可以添加拦截器在WebSocket连接建立和断开前进行一些额外操作。

  不管用哪种实现方式,都需要先导入jar包(如下),其中version根据实际springboot版本选择,避免冲突

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
    <!-- <version>1.3.5.RELEASE</version> -->
</dependency>

 

1.1 第一种实现方法

(1)WebSocket 业务逻辑实现。参数传递采用路径参数的方法,通过以下方式获取参数:

  • @ServerEndpoint("/testWebSocket/{id}/{name}")

  • public void onOpen(Session session, @PathParam("id") long id, @PathParam("name") String name)

import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RestController; @ServerEndpoint("/testWebSocket/{id}/{name}") @RestController public class TestWebSocket { // 用来记录当前连接数的变量 private static volatile int onlineCount = 0; // concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象 private static CopyOnWriteArraySet<TestWebSocket> webSocketSet = new CopyOnWriteArraySet<TestWebSocket>(); // 与某个客户端的连接会话,需要通过它来与客户端进行数据收发 private Session session; private static final Logger LOGGER = LoggerFactory.getLogger(TestWebSocket.class);
  @OnOpen
public void onOpen(Session session, @PathParam("id") long id, @PathParam("name") String name) throws Exception { this.session = session; System.out.println(this.session.getId()); webSocketSet.add(this); LOGGER.info("Open a websocket. id={}, name={}", id, name); } @OnClose public void onClose() { webSocketSet.remove(this); LOGGER.info("Close a websocket. "); } @OnMessage public void onMessage(String message, Session session) { LOGGER.info("Receive a message from client: " + message); } @OnError public void onError(Session session, Throwable error) { LOGGER.error("Error while websocket. ", error); } public void sendMessage(String message) throws Exception { if (this.session.isOpen()) { this.session.getBasicRemote().sendText("Send a message from server. "); } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { TestWebSocket.onlineCount++; } public static synchronized void subOnlineCount() { TestWebSocket.onlineCount--; } }

(2)配置ServerEndpointExporter,配置后会自动注册所有“@ServerEndpoint”注解声明的Websocket Endpoint

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
    
}

 

1.2 第二种实现方法

(1)WebSocket 业务逻辑实现。参数传递采用类似GET请求的方式传递,服务端的参数在拦截器中获取之后通过attributes传递给WebSocketHandler。

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;

@RestController
public class TestWebSocketController implements WebSocketHandler {
    
    private static AtomicInteger onlineCount = new AtomicInteger(0);
    
    private static final ArrayList<WebSocketSession> sessions = new ArrayList<>();
    
    private final Logger LOGGER = LoggerFactory.getLogger(TestWebSocketController.class);
    
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        sessions.add(session);
        int onlineNum = addOnlineCount();
        LOGGER.info("Oprn a WebSocket. Current connection number: " + onlineNum);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        sessions.remove(session);
        int onlineNum = subOnlineCount();
        LOGGER.info("Close a webSocket. Current connection number: " + onlineNum);
    }

    @Override
    public void handleMessage(WebSocketSession wsSession, WebSocketMessage<?> message) throws Exception {
        LOGGER.info("Receive a message from client: " + message.toString());
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        LOGGER.error("Exception occurs on webSocket connection. disconnecting....");
        if (session.isOpen()) {
            session.close();
        }
        sessions.remove(session);
        subOnlineCount();
    }

    /*
     * 是否支持消息拆分发送:如果接收的数据量比较大,最好打开(true), 否则可能会导致接收失败。
     * 如果出现WebSocket连接接收一次数据后就自动断开,应检查是否是这里的问题。
     */
    @Override
    public boolean supportsPartialMessages() {
        return true;
    }

    
    public static int getOnlineCount() {
        return onlineCount.get();
    }
    
    public static int addOnlineCount() {
        return onlineCount.incrementAndGet();
    }
    
    public static int subOnlineCount() {
        return onlineCount.decrementAndGet();
    }

}

(2)HandShake 拦截器实现

import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

public class TestHandShakeInterceptor extends HttpSessionHandshakeInterceptor {
    
    private final Logger LOGGER = LoggerFactory.getLogger(TestHandShakeInterceptor.class);
    
    /*
     * 在WebSocket连接建立之前的操作,以鉴权为例
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, 
            WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        
        LOGGER.info("Handle before webSocket connected. ");
        
        // 获取url传递的参数,通过attributes在Interceptor处理结束后传递给WebSocketHandler
        // WebSocketHandler可以通过WebSocketSession的getAttributes()方法获取参数
        ServletServerHttpRequest serverRequest = (ServletServerHttpRequest) request;
        String id = serverRequest.getServletRequest().getParameter("id");
        String name = serverRequest.getServletRequest().getParameter("name");

        if (tokenValidation.validateSign()) {
            LOGGER.info("Validation passed. WebSocket connecting.... ");
            attributes.put("id", id);
            attributes.put("name", name);
            return super.beforeHandshake(request, response, wsHandler, attributes);
        } else {
            LOGGER.error("Validation failed. WebSocket will not connect. ");
            return false;
        }
    }
    
    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
            WebSocketHandler wsHandler, Exception ex) {
        // 省略
    }

}

(3)WebSocket 配置类实现(注册WebSocket实现类,绑定接口,同时将实现类和拦截器绑定)

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
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 TestWebSocketController;
import TestHandShakeInterceptor;

@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {

    @Autowired
    private TestWebSocketController testWebSocketController;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(TestWebSocketController, "/testWebSocket")
                .addInterceptors(new TestHandShakeInterceptor()).setAllowedOrigins("*");
    }

}

 

1.3 补充说明

(1)在WebSocket实现过程中,尤其是通过“@ServerEndpoint”实现的时候,可能会出现注入失败的问题,即注入的Bean为null的问题。可以通过手动注入的方式来解决,需要改造实现类和SpringBoot启动类,如下:

@ServerEndpoint("testWebsocket")
@RestController
public class WebSocketController {

    private TestService testService;
    
    private static ApplicationContext applicationContext;
    
    @OnOpen
    public void onOpen(Session session) {
        testService = applicationContext.getBean(TestService.class);
    }

    @OnClose
    public void onClose() {}

    @OnMessage
    public void onMessage(String message, Session session) {}

    @OnError
    public void onError(Session session, Throwable error) {}

    public static void setApplicationContext(ApplicationContext applicationContext) {
        WebSocketController.applicationContext = applicationContext;
    }
    
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import WebSocketController;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
//        SpringApplication.run(Application.class, args);
        SpringApplication springApplication = new SpringApplication(Application.class);
        ConfigurableApplicationContext configurableApplicationContext = springApplication.run(args);
        WebSocketController.setApplicationContext(configurableApplicationContext);  // 解决WebSocket不能注入的问题
    }

}

 

2. 客户端的实现,我尝试了html和java WebSocketClient两种方式

2.1 html实现

<!DOCTYPE html>
<html>
<head>
    <title>WebSocket示例</title>
    <meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport' />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
    <input id="text" type="text"/>
    <button onclick="send()">发送消息</button>
    <hr/>
    <button onclick="closeWebSocket()">关闭WebSocket连接</button>
    <hr/>
    <div id="message"></div>
</body>

<script type="text/javascript">
    var websocket = null;
    //判断当前浏览器是否支持WebSocket
    if ('WebSocket' in window) {
        // 不带参数的写法
        websocket = new WebSocket("ws://127.0.0.1:18080/testWebsocket");
        // 通过路径传递参数的方法(服务端采用第一种方法"@ServerEndpoint"实现)
        websocket = new WebSocket("ws://127.0.0.1:18080/testWebsocket/23/Lebron");
        // 通过类似GET请求方式传递参数的方法(服务端采用第二种方法"WebSocketHandler"实现)
        websocket = new WebSocket("ws://127.0.0.1:18080/testWebsocket?id=23&name=Lebron");
    }
    else {
        alert('当前浏览器 Not support websocket')
    }

    //连接发生错误的回调方法
    websocket.onerror = function () {
        setMessageInnerHTML("WebSocket连接发生错误");
    };

    //连接成功建立的回调方法
    websocket.onopen = function () {
        setMessageInnerHTML("WebSocket连接成功");
    }

    //接收到消息的回调方法
    websocket.onmessage = function (event) {
        setMessageInnerHTML(event.data);
    }

    //连接关闭的回调方法
    websocket.onclose = function () {
        setMessageInnerHTML("WebSocket连接关闭");
    }

    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function () {
        closeWebSocket();
    }

    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }

    //关闭WebSocket连接
    function closeWebSocket() {
        websocket.close();
    }

    //发送消息
    function send() {
        var message = document.getElementById('text').value;
        websocket.send(message);
    }
</script>
</html>

 

2.2 Java WebSocketClient实现

(1)WebSocketClient 实现类

import java.net.URI;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft;
import org.java_websocket.handshake.ServerHandshake;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestWebSocketClient extends WebSocketClient {
    
    private final Logger LOGGER = LoggerFactory.getLogger(TestWebSocketClient.class);
    
    public TestWebSocketClient(URI serverUri) {
        super(serverUri);
    }
    
    public TestWebSocketClient(URI serverUri, Draft protocolDraft) {
        super(serverUri, protocolDraft);
    }

    @Override
    public void onOpen(ServerHandshake serverHandshake) {
        LOGGER.info("Open a WebSocket connection on client. ");
    }
    
    @Override
    public void onClose(int arg0, String arg1, boolean arg2) {
        LOGGER.info("Close a WebSocket connection on client. ");
    }

    @Override
    public void onMessage(String msg) {
        LOGGER.info("WebSocketClient receives a message: " + msg);
    }

    @Override
    public void onError(Exception exception) {
        LOGGER.error("WebSocketClient exception. ", exception);
    }

}

(2)WebSocketClient 发送数据

String serverUrl = "ws://127.0.0.1:18080/testWebsocket"
URI recognizeUri = new URI(serverUrl);
client = new TestWebSocketClient(recognizeUri, new Draft_6455());
client.connect();
client.send("This is a message from client. ");

 

作者:Strugglion
来源链接:https://www.cnblogs.com/strugglion/p/10021173.html

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/java/17214.html

标签: Spring Boot
分享给朋友:

“SpringBoot + WebSocket 开发笔记” 的相关文章

记一次SpringBoot项目使用RedisTemplate无法反序列化字节数组问题困扰8小时巨坑

记一次SpringBoot项目使用RedisTemplate无法反序列化字节数组问题困扰8小时巨坑

一、问题描述 问题描述: 前提:向Redis中SET值的是一个JFinal项目,使用的Jedis客户端API操作的,把一个对象转成byte[]形式存入Redis中。 然后在另一个SpringBoot项目中我通过Spring自带的RedisTempl...

SpringBoot项目改为SpringCloud项目使用nacos作为注册中心

SpringBoot项目改为SpringCloud项目使用nacos作为注册中心

本章讲解的是在不改变原有业务的情况下将springboot改为springcloud项目使用nacos作为注册中心 首先在官网下载好nacos注册中心,在bin目录中startup.cmd文件为启动命令默认端口号是8888 接下来修改原有项目依赖...

[springboot]Slf4j日志框架的体系结构

文章目录 一、五花八门的日志工具包 1.1. 日志框架 1.2.日志门面 1.3日志门面存在的意义...

SpringBoot之Hello World篇,使用eclipse+maven搭建SpringBoot工程

SpringBoot之Hello World篇,使用eclipse+maven搭建SpringBoot工程

首先来了解下什么是SpringBoot 百度百科:Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬...

SpringBoot之整合Mybatis篇

SpringBoot之整合Mybatis篇

开头语 啪嚓,醒木一拍(咳咳) ,上回书我们说到:SpringBoot的简单搭建,也就是SpringBoot的搭建流程,简单的将项目进行启动,本次,我们简单的把SpringBoot与持久层框架Mybatis进行整合,接下来,我们开始。 Mybatis名词解释   &...

SpringBoot整合Dubbo与zookeeper纯注解版

SpringBoot整合Dubbo与zookeeper纯注解版

一、Dubbo和zk的作用 上回讲到,Dubbo作为一款优秀的RPC框架,封装了dubbo-provider(提供者)和dubbo-consumer(消费者),而provider和consumer之间需要通过注册中心来作为可发现的服务目录。而zookeeper(此处简称zk)提供了服务接口注...

SpringBoot整合SpringCloud分布式服务

SpringBoot整合SpringCloud分布式服务

一、SpringCloud是什么 度娘:Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署...

SpringBoot整合消息队列工具kafka

SpringBoot整合消息队列工具kafka

一、前言 之前整理了kafka在windows下的安装过程,也通过shell命令进行了消息产生者和消息消费者的创建及消息发送,所以想到把kafka与最流行的SpringBoot的框架进行整合,与项目结合,进行消息的发送。 二、整合开始 1.SpringBoot工程搭建,此处不多讲,可以...

SpringBoot+Mybatis框架整合Shiro权限管理

SpringBoot+Mybatis框架整合Shiro权限管理

一、前言 ​ 之前曾分享过一个关于shiro的认证原理的文章,分享了一下shiro的认证流程与shiro中的名词解释,其实shiro作为一款轻量级框架,被应用在各种中小型及大型企业的登录认证和用户授权的模块,有小伙伴称,在用SpringBoot框架,之前用过xml方式进行配置shiro框架,...

SpringBoot 如何统一后端返回格式

SpringBoot 如何统一后端返回格式

在前后端分离的项目中后端返回的格式一定要友好,不然会对前端的开发人员带来很多的工作量。那么SpringBoot如何做到统一的后端返回格式呢?今天我们一起来看看。为什么要对SpringBoot返回统一的标准格式在默认情况下,SpringBoot的返回格式常见的有三种:返回String@GetMappi...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。