SpringBoot整合WebSocket实战演练

2023-10-19 09:50:48 浏览数 (2)

前言

本文将介绍如何在Spring Boot应用程序中使用WebSocket实现服务端向客户端推送消息。Spring Boot和WebSocket的整合实现服务端向客户端推送消息,使得客户端能够实时接收并处理服务器发来的信息。WebSocket协议是一种双向通信的网络协议,使得客户端和服务器能够建立持久连接,实现实时交互。通过WebSocket,客户端可以实时接收服务器推送的消息,并立即做出响应,而不需要等待服务器处理请求。这种实时的交互方式在Web应用中非常有用,特别是在需要实时更新用户界面、处理用户输入的场景中。

一、WebSocket介绍

在介绍WebSocket之前,我们先了解传统HTTPS协议,一般客户端请求接口都是通过HTTPS协议,HTTPS是短连接,HTTPS是在TCP基础上实现的,所以建立连接需要三次握手,四次挥手,是安全可靠的,并且必须客户端主动先服务端请求,没请求一次,服务端发送一次数据。所以对于实时数据更新,或者想要服务端给客户端发送数据,就没法实现。所以就需要一种协议,客户端和服务端都可以双向发送数据,就有了WebSocket协议。

WebSocket协议是一种双向通信的网络协议,它在单个TCP连接上提供全双工通信。与HTTP请求-响应模型不同,WebSocket允许服务器和客户端在连接建立后立即进行通信,而不需要等待服务器处理请求。WebSocket协议具有以下特点:

  1. 全双工通信:WebSocket支持全双工通信,即客户端和服务器可以在同一个连接上同时发送和接收数据。
  2. 持久连接:WebSocket连接是持久的,这意味着在客户端或服务器断开连接之前,它们可以保持连接状态。
  3. 客户端-服务器通信:WebSocket允许客户端和服务器之间进行双向通信,这使得客户端可以实时接收并处理服务器发送的消息。

WebSocket协议的主要优势在于它的简单性和灵活性。与传统的HTTP请求-响应模型相比,WebSocket协议允许客户端和服务器更快地建立连接,并更有效地处理实时数据。

它适用于以下使用场景:

1.实时通信:WebSocket协议支持全双工通信,可以在客户端和服务器之间实时发送和接收数据,因此适用于在线聊天、实时数据更新等场景。 2.Web游戏:WebSocket协议在Web游戏开发中也很常用,可以用于实时的游戏数据交换,如游戏状态、玩家输入等。 3.在线Web应用:WebSocket协议可以用于开发实时的Web应用,如股票交易行情分析、实时新闻等。 4.数据推送:WebSocket协议可以用于服务器向客户端推送数据,如实时通知、新的消息等。

二、WebSocket常用方法以及生命周期

传统WebSocket需要重写以下方法:connect(),onOpen(),doReceive(),onMessage(),doSend(),onClose(),对于新手可能不知道,而且现在开发基本也是采用spring框架。所以本文利用Spring中的TextWebSocketHandler,TextWebSocketHandler是Spring WebSocket中用于处理基于文本的WebSocket消息的接口,它提供了一些方法来处理WebSocket会话的各个阶段,使用只要继承TextWebSocketHandler类就行。下面是TextWebSocketHandler常用的方法和流程:

  1. afterConnectionEstablished():当客户端建立连接时调用,用于执行连接建立后的操作。
  2. handleTextMessage():当接收到消息时调用,用于处理客户端发送的消息。
  3. handleTransportError():当连接发生错误时调用,用于处理连接错误。
  4. afterConnectionClosed():当连接关闭时调用,用于执行连接关闭后的操作。

以下是TextWebSocketHandler实例:

代码语言:javascript复制
@Component
public class WebSocketHandler extends TextWebSocketHandler {

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 连接建立后执行的操作
        System.out.println("Connection established: "   session.getId());
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 处理接收到的消息
        System.out.println("Received message: "   message.getPayload());
        
        // 向客户端发送响应
        session.sendMessage(new TextMessage("Hello, client!"));
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        // 处理连接错误
        exception.printStackTrace();
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        // 连接关闭后执行的操作
        System.out.println("Connection closed: "   session.getId());
    }
}

TextWebSocketHandle生命周期,包括客户端和服务端,客户端使用WebSocketSession与客户端建立连接,服务端客户端都可以调用WebSocketSession对象,包括关闭连接close,服务端调用sendMessage方法发送消息,具体如图所示:

三、SpringBoot整合WebSocket

上面我们简单介绍了WebSocket的以及TextWebSocketHandle的生命周期,接下来,我们就可以利用Springboot整合WebSocket了。

1.引入WebSocket依赖

主要是引入websocket依赖

代码语言:javascript复制
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

2.新增websocket配置WebSocketConfig

新增配置类,实现WebSocketConfigurer ,主要配置websocket的注册连接地址,客户端连接ws://ip:端口/websocket/game,就会连接到改socket

代码语言:javascript复制
@Component
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Autowired
    private WebSocketHandler webSocketHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(webSocketHandler, "/websocket/game")
                .addInterceptors(new WebSocketInterceptor());
    }
}

3.新增websocket处理器WebSocketHandler

WebSocketHandler就是监听websocket连接之后的操作,也是上面继承的TextWebSocketHandle,并且根据上面的什么周期,我们只要在原有的基础上进行业务处理就行了,本文模拟玩游戏,客户端连接之后,服务端扣减用户试玩的时长,当时长没有了服务端主动推送websocket消息给客户端,断开连接。具体代码如下:

代码语言:javascript复制
@Component
public class WebSocketHandler extends TextWebSocketHandler implements InitializingBean {

    private static final Logger logger = LoggerFactory.getLogger(WebSocketHandler.class);
    // 连接列表
    private static Map<String, WebSocketSession> sessionCache = new HashMap<>();
    // 定时任务
    private static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

    @Override
    public void afterPropertiesSet() throws Exception {
        // 定时任务
        scheduler.scheduleAtFixedRate(() -> {
            try {
                // 扣减时长
                deduct();
            } catch (Exception e) {

            }
        }, 30,60, TimeUnit.SECONDS);
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 连接建立后执行的操作
        System.out.println("连接成功: "   session.getId());
        // 放到缓存连接列表
        Map<String, Object> attributes = session.getAttributes();
        Object objUid = attributes.get("uid");
        String uid = objUid.toString();
        sessionCache.put(uid, session);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 处理接收到的消息
        System.out.println("服务端接收消息: "   message.getPayload());

        // 向客户端发送响应
        sendMessage(session,"Hello, client!");
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        // 处理连接错误
        exception.printStackTrace();
        closeSession(session,"关闭连接");
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        // 连接关闭后执行的操作
        closeSession(session,"关闭连接");
        // 删除缓存连接列表
        Map<String, Object> attributes = session.getAttributes();
        Object objUid = attributes.get("uid");
        String uid = objUid.toString();
        sessionCache.remove(uid);
    }

    /**
     * 发送消息
     * @param session
     * @param message
     */
    private void sendMessage(WebSocketSession session, String message) {
        if (session != null && session.isOpen()) {
            try {
                session.sendMessage(new TextMessage(message));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void closeSession(WebSocketSession session, String message) {
        if (message != null) {
            sendMessage(session, message);
        }
        try {
            if (session.isOpen()) {
                session.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }



    private void deduct() {
        // todo 业务处理。。。。
        String uid = "123";
        WebSocketSession session = sessionCache.get(uid);
        sendMessage(session, "扣减时长");
    }

}

四、在线测试

websocket协议调用调试跟HTTP不一样,不能直接用浏览器调用,推荐在线小工具调试:WebSocket在线测试_在线模拟websocket请求工具

启动springboot工程服务,在调试工具输入地址:ws://localhost:8080/websocket/game?uid=123

调试工具模拟客户端先服务端发送消息:

由于我们模拟了扣减游戏时长,会发现,服务端有定时任务,每个60s扣取时长,并且发送消息通知客户端:

以上就完成websocket的整合,算是比较简单,但是如果需要结合业务处理,就有很多细节要调整了,

五、关于WebSocket的细节处理

1.WebSocket资源分配问题

由于websocket连接,其实是生成一个长连接在内存中,如果用户连接过多的话,可能会造成资源不足,所以在实际业务处理中,一般会设置连接大小,或者其他分配介质,如果超过了,就有放到队列中进行等待操作。

2.WebSocket连接心跳问题

WebSocket维护心跳是必要的,因为它可以保持连接的活性,防止连接超时断开,并确保实时消息传输。并且对于长时间没有心跳的连接要及时断开,防止占用内存。一般通过约定协议,定时客户端向服务端发送消息,将消息的标识存在到缓存,设定一定的时间,服务端每次接收到客户端心跳消息,就更新缓存时间,这样缓存就一直存在,否则,服务端将断开连接。可以使用本地缓存caffeine或者redis缓存。

总结

本文主要讲解了SpringBoot月websocket的实战,但是对于websocket的使用也是有优缺点的。

优点:整合了SpringBoo的WebSocket可以提供更加灵活和强大的实时通信功能。开发者可以快速实现实时通信、在线协作等功能,减少了开发时间和代码复杂度。同时,WebSocket的双向通信能力可以实现服务器主动推送数据,提高了应用程序的实时性和响应速度。

缺点:WebSocket是基于TCP的协议,相对于HTTP的短连接,它需要更多的网络资源和计算资源。同时,由于WebSocket连接的长时间保持,如果大量用户同时连接服务器,可能会对服务器造成较大压力。此外,WebSocket的协议相对复杂,开发者需要额外学习。

0 人点赞