目录
一、了解 WebSocket 协议
WebSocket 是一种全双工、长连接的通信协议,旨在提供实时通信能力。在使用 WebSocket 之前,建议先学习 WebSocket 协议的基本知识。
WebSocket特点
实时通信:
WebSocket 提供了一种在客户端和服务器之间进行实时通信的机制。与传统的 HTTP 请求-响应模式不同,WebSocket 允许服务器主动向客户端推送消息,而不需要客户端发起请求。这使得实时聊天、实时数据更新等实时应用成为可能。
双向通信:
WebSocket 可以实现双向通信,客户端和服务器可以同时发送和接收消息。无论是在客户端还是在服务器端,都可以通过发送消息来进行实时更新、通知和响应。这使得开发者可以更方便地构建交互性强、响应快速的应用程序。
长连接:
传统的 HTTP 请求-响应模式使用短连接,即每次请求都需要建立新的连接和关闭连接。相比之下,WebSocket 使用长连接,即一次连接可以持续有效并传输多个消息。这减少了连接建立和关闭的开销,并减少了网络流量和延迟,从而提高了应用的效率和性能。
跨域通信:
WebSocket 协议支持跨域通信,也就是在不同域名或不同端口之间进行通信。这使得开发者可以构建分布式系统,将不同模块或服务放置在不同的主机上,并通过 WebSocket 进行实时通信和数据交换。
二、后端代码实现
引入maven依赖
<!-- socket启动依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>2.7.0</version>
</dependency>
WebSocketConfig
WebSocketConfig作用:
开启 WebSocket 的支持,并将该类注入到 Spring 容器中。具体来说,它通过在 Spring 配置文件中注册一个 ServerEndpointExporter Bean,用于扫描所有带有 @ServerEndpoint 注解的 WebSocket 服务,并将其注册到 WebSocket 容器中。这样就可以在 Spring Boot 应用程序中使用 WebSocket 实现实时通信和数据传输了。
WebSocketConfig.java代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* 开启WebSocket的支持,并把该类注入到spring容器中
* @description: WebSocket配置
* @author: 戈壁老孙
* @create: 2023/11/08 10:28
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
MyWebSocket
@注解解释
@ServerEndpoint("/websocket")
注解表示将该类标识为一个 WebSocket 服务端端点,客户端可以通过该端点进行 WebSocket 连接。/websocket
是客户端连接的 URL 路径。
@EnableScheduling
注解表示启用 Spring 的定时任务调度功能。在该类中使用的 @Scheduled
注解才能生效。
@Component
注解表示将该类标识为一个 Spring 组件,由 Spring 自动进行管理和注入。
综合起来,使用这些注解标注的类为一个 WebSocket 服务端的实现类,同时支持定时任务调度,并由 Spring 自动进行管理和注入。客户端可以通过访问 /websocket
路径来与该 WebSocket 服务端建立连接。
MyWebSocket作用:
使用了 @ServerEndpoint 注解声明 websocket 服务端的地址。在收到客户端发来的消息时,会进入 onMessage 方法,进行消息的处理。同时,在客户端进行连接或关闭连接的时候,则会进入对应的 onOpen 和 onClose 方法。此外,使用了一个静态变量 onlineCount 表示当前连接的 WebSocket 数量,并使用 CopyOnWriteArraySet 类型的 webSocketSet 保存所有连接的 WebSocket 实例。在加入连接时,调用 addOnlineCount() 增加在线人数记录,退出连接时,调用 subOnlineCount() 减少在线人数记录。最后,使用了 @Autowired 注解注入IProTriaxialDataService 的 bean,用于处理业务逻辑。
MyWebSocket.java代码
import com.alibaba.fastjson2.JSONObject;
import com.unis.common.utils.StringUtils;
import com.unis.web.domain.ProTriaxialData;
import com.unis.web.service.IProTriaxialDataService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* socket 通信类
* @description: WebSocket配置
* @author: 戈壁老孙
* @create: 2023/11/08 10:30
*/
@ServerEndpoint("/websocket")
@EnableScheduling
@Component
public class MyWebSocket {
private static final Logger log = LoggerFactory.getLogger(MyWebSocket.class);
private static int onlineCount = 0;
private static CopyOnWriteArraySet<MyWebSocket> webSocketSet = new CopyOnWriteArraySet<>();
private Session session;
private Boolean isReceive = true;
//业务注入
private static IProTriaxialDataService proTriaxialDataService;
@Autowired
public void proTriaxialDataService(IProTriaxialDataService proTriaxialDataService){
//WebScoketController是该类的类名 需要换成自己的类名
MyWebSocket.proTriaxialDataService = proTriaxialDataService;
}
@OnOpen
public void onOpen(Session session) {
this.session = session;
webSocketSet.add(this);
addOnlineCount();
log.info("有新链接加入!当前在线人数:{}",getOnlineCount());
}
@OnClose
public void onClose() {
webSocketSet.remove(this);
subOnlineCount();
log.info("有一链接关闭!当前在线人数:{}",getOnlineCount());
}
@OnMessage
public void onMessage(String message, Session session) throws IOException {
log.info("socket 接收到信息{}",message);
//判断前端是否已消费上一条信息
if (StringUtils.isNotEmpty(message) && message.equals("已消费")){
isReceive = true;
} else {
// 连接已断开
isReceive = false;
}
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
@Scheduled(cron = "*/5 * * * * *")
public void sendMessageScheduledTask()throws Exception{
log.info("socket 执行三数据推送给前端");
try{
if (isReceive){
//每五秒发送一次
List<ProTriaxialData> proTriaxialData = proTriaxialDataService.selectProTriaxialDataByCreateTimeDesc();
if (proTriaxialData!=null && proTriaxialData.size()>0){
String message = JSONObject.toJSONString(proTriaxialData.get(0));
for (MyWebSocket item : webSocketSet) {
item.sendMessage(message);
}
}
}
log.info("socket 数据发送成功!");
}catch (Exception e){
log.info("socket 数据发送失败!");
e.printStackTrace();
}
}
public static synchronized int getOnlineCount() {
return MyWebSocket.onlineCount;
}
public static synchronized void addOnlineCount() {
MyWebSocket.onlineCount++;
}
public static synchronized void subOnlineCount() {
MyWebSocket.onlineCount--;
}
}
三测试服务端是否正常连接
1、打开websocket测试小工具
2、在测试小工具中输入访问地址,点击连接,如下图
ws://IP:PORT/websocket
四、前端代码实现
created() {
var ws = new WebSocket("ws://IP:PORT/websocket");
// 连接成功后的回调函数
ws.onopen = function (params) {
console.log('客户端连接成功')
// 向服务器发送消息
ws.send('认证成功!')
};
// 从服务器接受到信息时的回调函数
ws.onmessage = function (e) {
console.log('收到服务器响应', e.data)
this.proTriaxialData = e.data;
console.log( this.proTriaxialData);
};
// 连接关闭后的回调函数
ws.onclose = function(evt) {
console.log("关闭客户端连接");
};
// 连接失败后的回调函数
ws.onerror = function (evt) {
console.log("连接失败了");
};
// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,这样服务端会抛异常。
window.onbeforeunload = function() {
ws.close();
}
console.log(ws)
}
五、效果展示
到这里就结束啦~