Spring Boot整合WebSocket实现双向实时通讯简单示例

217 阅读1分钟

使用 WebSocket 能让客户端与服务器端进行双向的实时通信。当客户端与服务器端之间交互的内容较多或对实时性要求较高时,可使用 WebSocket。目前主流的浏览器都支持WebSocket。

一、引入 Maven 依赖

<!-- websocket依赖 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 工具类库 -->
<dependency>
	<groupId>cn.hutool</groupId>
	<artifactId>hutool-all</artifactId>
	<version>5.7.16</version>
</dependency>
<!-- JSON类库 -->
<dependency>
	<groupId>com.google.code.gson</groupId>
	<artifactId>gson</artifactId>
</dependency>

二、新建一个 WebSocket 处理类

WebSocketEndPoint.java

import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
@ServerEndpoint("/websocket/{name}")
public class WebSocketEndPoint {

    private static Map<Session, String> socketMap = new ConcurrentHashMap<>();

    // 连接建立成功时触发的方法
    @OnOpen
    public void onOpen(@PathParam("name") String name, Session session) {
        socketMap.put(session, name);
        log.info("当前连接数:{}", socketMap.keySet().size());
    }

    // 关闭时触发的方法
    @OnClose
    public void onClose(Session session) {
        socketMap.remove(session);
        log.info("当前连接数:{}", socketMap.keySet().size());
    }

    // 收到客户端消息时触发的方法
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到来自{}的消息:{}", session, message);
        String name = socketMap.get(session);
        Dict data = Dict.create();
        data.set("state", 1);
        try {
            // 遍历所有建立连接的客户端,给其它客户端发送消息
            for (Session client : socketMap.keySet()) {
                if (StrUtil.equals(socketMap.get(client), name)) {
                    continue;
                }
                client.getBasicRemote().sendText(message);
            }
        } catch (Exception e) {
            log.error("向客户端发送消息时发生错误:", e);
        }
    }

    // 发生错误时触发的方法
    @OnError
    public void onError(Session session, Throwable throwable) {
        socketMap.remove(session);
        log.info("当前连接数:{}", socketMap.keySet().size());
    }

}

WebSocket 处理类中我们定义了4个方法:

WebSocket处理类

onOpen()方法定义了一个用@PathParam修饰的形参,通过该形参可以获取路径参数。

三、新建 WebSocket 配置类

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

@Configuration
public class WebSocketConfig {

    // 专门负责检测所有带@ServerEndpoint注解的类,并注册
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

四、创建客户端页面

本示例主要包含两个客户端页面(computer客户端和mobile客户)。

couputer.html —— computer客户端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Computer</title>
    <link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
    <div id="app">
        <!-- 展示接收到的消息 -->
        <div class="msg">{{ msg }}</div>
        <div class="button-wrapper">
            <!-- 连接按钮 -->
            <button @click="connect">连接</button>
            <!-- 关闭连接按钮 -->
            <button @click="disconnect">关闭连接</button>
            <!-- 发送消息按钮 -->
            <button @click="sendMsg">准备好了</button>
        </div>
    </div>
    <!-- 引入Vue3 -->
    <script src="js/vue.global.js"></script>
    <script>
        const App = {
            data() {
                return {
                    websocket: null,
                    // 接收到的消息内容
                    msg: ''
                }
            },
            methods: {
                // 发送消息
                sendMsg() {
                    console.log('send msg.')
                    if (this.websocket == null || this.websocket.readyState != 1) {
                        alert('请先连接websocket服务器!')
                        return
                    }
                    var data = {
                        msg: '准备好了,登记吧',
                        state: 1
                    }
                    this.websocket.send(JSON.stringify(data))
                },
                // 连接
                connect() {
                    console.log('connect.')
                    var _this = this;
                    if (this.websocket && this.websocket.readyState == 1) {
                        this.websocket.close()
                        this.connectBtnText = '连接服务器'
                    }
                    this.websocket = new WebSocket('ws://127.0.0.1:18528/websocket/computer')
                    this.websocket.onopen = function () {
                        this.connectBtnText = '关闭服务器'
                    }
                    this.websocket.onmessage = function (event) {
                        console.log(event.data)
                        debugger
                        var data = JSON.parse(event.data)
                        if (data.state == 1) {
                            _this.msg = data.msg
                            console.log(_this.msg)
                        }
                    }
                },
                // 关闭连接
                disconnect() {
                    console.log('disconnect.')
                    this.websocket.close()
                }
            }
        }
        Vue.createApp(App).mount('#app');
    </script>

</body>
</html>

mobile.html —— mobile客户端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Mobile</title>
    <link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
    <div id="app">
        <div class="msg">{{ msg }}</div>
        <div class="button-wrapper">
            <button @click="connect">连接</button>
            <button @click="disconnect">关闭连接</button>
            <button @click="sendMsg()" :disabled="state !== 1">登记</button>
        </div>
    </div>
    <script src="js/vue.global.js"></script>
    <script>
        const App = {
            data() {
                return {
                    counter: 0,
                    connectBtnText: '连接服务器',
                    websocket: null,
                    state: 0,
                    msg: ''
                }
            },
            methods: {
                sendMsg() {
                    console.log('send msg.')
                    if (this.websocket == null || this.websocket.readyState != 1) {
                        alert('请先连接websocket服务器!')
                        return
                    }
                    this.websocket.send(JSON.stringify({
                        state: 1,
                        msg: '已登记'
                    }))
                },
                connect() {
                    console.log('connect.')
                    var _this = this
                    if (this.websocket && this.websocket.readyState == 1) {
                        this.websocket.close()
                        this.connectBtnText = '连接服务器'
                    }
                    this.websocket = new WebSocket('ws://127.0.0.1:18528/websocket/mobile')
                    this.websocket.onopen = function () {
                        this.connectBtnText = '关闭服务器'
                    }
                    this.websocket.onmessage = function (event) {
                        console.log(event.data)
                        var data = JSON.parse(event.data)
                        if (data.state) {
                            _this.state = data.state
                            _this.msg = data.msg
                            console.log(_this.state == 1)
                            console.log(_this.msg)
                        }
                    }
                },
                disconnect() {
                    console.log('disconnect.')
                    this.websocket.close()
                }
            }
        }
        Vue.createApp(App).mount('#app');
    </script>
</body>
</html>

五、效果演示

详细操作流程:

(1)在浏览器中打开computer客户端页面,页面地址:http://localhost:18528/computer.html,点击连接按钮;

(2)在浏览器中另起一个标签页,打开mobile客户端页面,页面地址:http://localhost:18528/mobile.html,点击连接按钮,登记按钮此时不可用;

(3)computer客户端页面:点击准备好了按钮发送消息给mobile客户端;

(4)mobile客户端页面:此时登记按钮已变为可用,消息面板中也已经显示了computer客户端发送来的消息,点击登记按钮发送消息给computer客户端;

(5)computer客户端页面:消息面板中也已经显示了mobile客户端发送来的消息。

消息处理的时序图如下:

sequenceDiagram
	participant computer客户端
	participant WebSocket处理类
	participant mobile客户端
        computer客户端->>WebSocket处理类: 1.computer端准备就绪,发送消息
        WebSocket处理类->>mobile客户端: 2.将消息发送到mobile端
        mobile客户端->>mobile客户端: 3.更改登记按钮状态,将状态调整为可用,显示接收到的消息
        mobile客户端-->>WebSocket处理类: 4.点击登记按钮,发送消息
        WebSocket处理类-->>computer客户端: 5.将消息发送到computer端
        computer客户端->>computer客户端: 6.显示接收到的消息

示例源码位置:https://github.com/wanggch/learn-springboot/tree/master/learn-springboot-websocket