【Java】Spring Boot整合WebSocket

本文介绍了如何在SpringBoot项目中集成WebSocket协议,以实现客户端和服务器的双向实时通信。通过添加依赖、配置WebSocket并编写控制器,展示了WebSocket的基本使用,包括@ServerEndpoint注解的使用以及连接打开、关闭、消息处理的回调方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【Java】Spring Boot整合WebSocket

WebSocket简介

WebSocket是一种协议,用于实现客户端和服务器之间的双向通信。它可以在单个TCP连接上提供全双工通信,避免了HTTP协议中的请求-响应模式,从而实现更高效的数据交换。WebSocket协议最初由HTML5规范提出,现在已成为一种通用的网络协议,被广泛用于Web应用程序中。

WebSocket协议的主要特点包括:

  1. 建立在TCP上:WebSocket协议使用单个TCP连接进行全双工通信,避免了HTTP协议中的多次连接建立和断开操作,从而减少了网络延迟和带宽消耗。
  2. 双向通信:WebSocket协议支持双向通信,即客户端和服务器可以同时向对方发送和接收数据,实现更加灵活和高效的数据交换。
  3. 实时性:WebSocket协议可以实现实时通信,支持消息推送、实时聊天等功能,为Web应用程序带来更好的用户体验。
  4. 协议标准化:WebSocket协议已经被标准化,并且被广泛支持,几乎所有的现代浏览器都支持WebSocket协议。

WebSocket协议在Web应用程序中广泛使用,例如实现实时通信、在线游戏、即时消息等功能。开发者可以使用JavaScript编写客户端代码,使用Java、Node.js等语言编写服务器端代码,实现WebSocket协议的双向通信。

Pom

本次Spring Boot版本 2.7.8,WebSocket 版本 5.3.25.

parent

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.8</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

dependency

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

配置文件

WebSocketConfig

package cn.com.codingce.demo.config;

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

@Configuration
// 开启 WebSocket 支持
@EnableWebSocket
public class WebSocketConfig {

    /**
     * 必须要有的
     *
     * @return serverEndpointExporter
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

    /**
     * WebSocket 配置信息
     *
     * @return servletServerContainerFactoryBean
     */
    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean bean = new ServletServerContainerFactoryBean();
        
        // 文本缓冲区大小
        bean.setMaxTextMessageBufferSize(8192);
        // 字节缓冲区大小
        bean.setMaxBinaryMessageBufferSize(8192);

        return bean;
    }

}

使用

WebSocket 的注解:

注解作用备注
@ServerEndpoint用于声明WebSocket响应类,有点像@RequestMapping@ServerEndpoint(“/websocket”)
@OnOpenWebSocket连接时触发参数有:Session session, EndpointConfig config
@OnMessage有消息时触发参数很多,一会再说
@OnClose连接关闭时触发参数有:Session session, CloseReason closeReason
@OnError有异常时触发参数有:Session session, Throwable throwable

Controller

BaseWebSocketController

package cn.com.codingce.demo.conrtoller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;

import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author ma
 */
@Controller
@ServerEndpoint("/websocket")
public class BaseWebSocketController {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    // ConcurrentHashMap, 保证线程安全, static全局共享 session

    // 这里之所以static,是因为这个类不是单例的!!
    // 它虽然有@Controller注解,但是不适用Ioc容器中拿对象,每一次请求过来都是一个新的对象

    /**
     * 存放 session
     */
    private final static Map<String, Session> SESSION_MAP = new ConcurrentHashMap<>();

    /**
     * OnOpen 在连接创建(用户进入聊天室)时触发
     *
     * @param session session
     * @param config  config
     */
    @OnOpen
    public void openSession(Session session, EndpointConfig config) {
        // 将session存起来, 用于服务器向浏览器发送消息
        SESSION_MAP.put(session.getId(), session);
        String res = "OnOpen [" + session.getId() + "]进入房间";
        sendAll(res);
        logger.info(res);
    }


    /**
     * 响应字符串
     *
     * @param session session
     * @param message message
     */
    @OnMessage
    public void onMessage(Session session, String message) {
        String res = "OnMessage [" + session.getId() + "]" + message;
        sendAll(res);
        logger.info(res);
    }

    /**
     * 响应字节流
     *
     * @param session session
     * @param message message
     */
    @OnMessage
    public void onMessage(Session session, byte[] message) {
        // 这个以后再说
    }

    /**
     * OnClose 在连接断开(用户离开聊天室)时触发
     *
     * @param session     session
     * @param closeReason closeReason
     */
    @OnClose
    public void closeSession(Session session, CloseReason closeReason) {
        //记得移除相对应的session
        SESSION_MAP.remove(session.getId());

        String res = "OnClose [" + session.getId() + "]离开了房间";
        sendAll(res);
        logger.info(res);
    }

    @OnError
    public void sessionError(Session session, Throwable throwable) {
        // 通常有异常会关闭 session
        try {
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * sendAll
     *
     * @param message message
     */
    private void sendAll(String message) {
        for (Session s : SESSION_MAP.values()) {
            // 获得session发送消息的对象
            // Basic是同步, 会阻塞
            // Async是异步, 这个会有多线程并发导致异常, 发送消息太快也会有并发异常, 需要有 消息队列 来辅助使用
            final RemoteEndpoint.Basic remote = s.getBasicRemote();

            try {
                // 发送消息
                remote.sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

前端

Conrtoller

package cn.com.codingce.demo.conrtoller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class IndexController {

    @RequestMapping("/index")
    public String indexDefault(Model model) {
        return "index";
    }

}

web

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>websocket-demo</title>

    <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.2.1/css/bootstrap.min.css">
</head>
<body>
<div class="container py-3">

    <div class="row">

        <div class="col-6">
            <div>
                <label for="messageArea">聊天信息:</label>
            </div>
            <div>
                <textarea id="messageArea" readonly class="w-100" style="height: 75vh;"></textarea>
            </div>
        </div>

        <div class="col">

            <div class="my-1">
                <label for="messageArea">用 户 名:</label>
            </div>

            <div class="my-1">
                <input type="text" id="username" autocomplete="off">
            </div>

            <div class="my-1">
                <button class="btn-info" id="joinRoomBtn">进入聊天室</button>
                <button class="btn-warning" id="leaveRoomBtn">离开聊天室</button>
            </div>

            <hr/>

            <div class="my-1">
                <label for="sendMessage">输入消息:</label>
            </div>
            <div>
                <textarea id="sendMessage" rows="5" class="w-100" style="max-height: 50vh"></textarea>
            </div>

            <div class="my-1">
                <button class="btn-primary" id="sendBtn">发送消息</button>
            </div>

        </div>

    </div>

</div>
]

<script src="https://s3.pstatp.com/cdn/expire-1-M/jquery/3.3.1/jquery.min.js"></script>

<script>
    let webSocket;
    // ip和端口号用自己项目的
    // {websocket}: 其实是刚刚那个@ServerEndpoint("/websocket")中定义的
    let url = 'ws://127.0.0.1:8091/websocket';

    $('#username').keyup(function (e) {
        let keycode = e.which;
        if (keycode === 13) {
            $('#joinRoomBtn').click();
        }
    });

    // 进入聊天室
    $('#joinRoomBtn').click(function () {
        let username = $('#username').val();
        webSocket = new WebSocket(url);
        webSocket.onopen = function () {
            console.log('webSocket连接创建。。。');
        }
        webSocket.onclose = function () {
            console.log('webSocket已断开。。。');
            $('#messageArea').append('websocket已断开\n');
        }
        webSocket.onmessage = function (event) {
            $('#messageArea').append(event.data + '\n');
        }
        webSocket.onerror = function (event) {
            console.log(event)
            console.log('webSocket连接异常。。。');
        }
    });

    // 退出聊天室
    $('#leaveRoomBtn').click(function () {
        if (webSocket) {
            // 关闭连接
            webSocket.close();
        }
    });

    // 发送消息
    $('#sendBtn').click(function () {
        var msg = $('#sendMessage').val();
        if (msg.trim().length === 0) {
            alert('请输入内容');
            return;
        }
        webSocket.send($('#sendMessage').val());

        $('#sendMessage').val('');
    });

</script>

</body>
</html>

测试

页面

在这里插入图片描述

用户一

在这里插入图片描述

用户二

在这里插入图片描述

用户一收到的信息

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后端码匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值