使用 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个方法:
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