前言
本文将介绍如何在Spring Boot应用程序中使用WebSocket实现服务端向客户端推送消息。Spring Boot和WebSocket的整合实现服务端向客户端推送消息,使得客户端能够实时接收并处理服务器发来的信息。WebSocket协议是一种双向通信的网络协议,使得客户端和服务器能够建立持久连接,实现实时交互。通过WebSocket,客户端可以实时接收服务器推送的消息,并立即做出响应,而不需要等待服务器处理请求。这种实时的交互方式在Web应用中非常有用,特别是在需要实时更新用户界面、处理用户输入的场景中。
一、WebSocket介绍
在介绍WebSocket之前,我们先了解传统HTTPS协议,一般客户端请求接口都是通过HTTPS协议,HTTPS是短连接,HTTPS是在TCP基础上实现的,所以建立连接需要三次握手,四次挥手,是安全可靠的,并且必须客户端主动先服务端请求,没请求一次,服务端发送一次数据。所以对于实时数据更新,或者想要服务端给客户端发送数据,就没法实现。所以就需要一种协议,客户端和服务端都可以双向发送数据,就有了WebSocket协议。
WebSocket协议是一种双向通信的网络协议,它在单个TCP连接上提供全双工通信。与HTTP请求-响应模型不同,WebSocket允许服务器和客户端在连接建立后立即进行通信,而不需要等待服务器处理请求。WebSocket协议具有以下特点:
- 全双工通信:WebSocket支持全双工通信,即客户端和服务器可以在同一个连接上同时发送和接收数据。
- 持久连接:WebSocket连接是持久的,这意味着在客户端或服务器断开连接之前,它们可以保持连接状态。
- 客户端-服务器通信: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常用的方法和流程:
afterConnectionEstablished()
:当客户端建立连接时调用,用于执行连接建立后的操作。handleTextMessage()
:当接收到消息时调用,用于处理客户端发送的消息。handleTransportError()
:当连接发生错误时调用,用于处理连接错误。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的协议相对复杂,开发者需要额外学习。