Spring WebSocket实现实时通信的详细教程
简介
WebSocket 是基于TCP/IP协议,独立于HTTP协议的通信协议。WebSocket 连接允许客户端和服务器之间的全双工通信,以便任何一方都可以通过已建立的连接将数据推送到另一方。
我们常用的HTTP是客户端通过「请求-响应」的方式与服务器建立通信的,必须是客户端主动触发的行为,服务端只是做好接口被动等待请求。而在某些场景下的动作,是需要服务端主动触发的,比如向客户端发送消息、实时通讯、远程控制等。客户端是不知道这些动作几时触发的,假如用HTTP的方式,那么设备端需要不断轮询服务端,这样的方式对服务器压力太大,同时产生很多无效请求,且具有延迟性。于是才采用可以建立双向通讯的长连接协议。通过握手建立连接后,服务端可以实时发送数据与指令到设备端,服务器压力小。
Spring WebSocket是Spring框架的一部分,提供了在Web应用程序中实现实时双向通信的能力。本教程将引导你通过一个简单的例子,演示如何使用Spring WebSocket建立一个实时通信应用。
准备工作
确保你的项目中已经引入了Spring框架的WebSocket模块。你可以通过Maven添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
创建WebSocket配置类(实现WebSocketConfigurer接口)
首先,创建一个配置类,用于配置WebSocket的相关设置。
package com.ci.erp.human.config;
import com.ci.erp.human.handler.WebSocketHandler;
import com.ci.erp.human.interceptor.WebSocketHandleInterceptor;
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.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
*
* Websocket配置类
*
* @author lucky_fd
* @since 2024-01-17
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注册websocket处理器和拦截器
registry.addHandler(webSocketHandler(), "/websocket/server")
.addInterceptors(webSocketHandleInterceptor()).setAllowedOrigins("*");
registry.addHandler(webSocketHandler(), "/sockjs/server").setAllowedOrigins("*")
.addInterceptors(webSocketHandleInterceptor()).withSockJS();
}
@Bean
public WebSocketHandler webSocketHandler() {
return new WebSocketHandler();
}
@Bean
public WebSocketHandleInterceptor webSocketHandleInterceptor() {
return new WebSocketHandleInterceptor();
}
}
上面的配置类使用@EnableWebSocket注解启用WebSocket,并通过registerWebSocketHandlers方法注册WebSocket处理器。
registerWebSocketHandlers:这个方法是向spring容器注册一个handler处理器及对应映射地址,可以理解成MVC的Handler(控制器方法),websocket客户端通过请求的url查找处理器进行处理
addInterceptors:拦截器,当建立websocket连接的时候,我们可以通过继承spring的HttpSessionHandshakeInterceptor来做一些事情。
setAllowedOrigins:跨域设置,
*表示所有域名都可以,不限制, 域包括ip:port, 指定*可以是任意的域名,不加的话默认localhost+本服务端口withSockJS: 这个是应对浏览器不支持websocket协议的时候降级为轮询的处理。
创建WebSocket消息处理器(实现TextWebSocketHandler 接口)
接下来,创建一个消息处理器,处理客户端发送的消息。
package com.ci.erp.human.handler;
import cn.hutool.core.util.ObjectUtil;
import com.ci.erp.common.core.utils.JsonUtils;
import com.ci.erp.human.domain.thirdVo.YYHeartbeat;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
*
* websocket处理类
* 实现WebSocketHandler接口
*
* - websocket建立连接后执行afterConnectionEstablished回调接口
* - websocket关闭连接后执行afterConnectionClosed回调接口
* - websocket接收客户端消息执行handleTextMessage接口
* - websocket传输异常时执行handleTransportError接口
*
* @author lucky_fd
* @since 2024-01-17
*/
public class WebSocketHandler extends TextWebSocketHandler {
/**
* 存储websocket客户端连接
* */
private static final Map<String, WebSocketSession> connections = new HashMap<>();
/**
* 建立连接后触发
* */
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("成功建立websocket连接");
// 建立连接后将连接以键值对方式存储,便于后期向客户端发送消息
// 以客户端连接的唯一标识为key,可以通过客户端发送唯一标识
connections.put(session.getRemoteAddress().getHostName(), session);
System.out.println("当前客户端连接数:" + connections.size());
}
/**
* 接收消息
* */
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
System.out.println("收到消息: " + message.getPayload());
// 收到客户端请求消息后进行相应业务处理,返回结果
this.sendMessage(session.getRemoteAddress().getHostName(),new TextMessage("收到消息: " + message.getPayload()));
}
/**
* 传输异常处理
* */
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
super.handleTransportError(session, exception);
}
/**
* 关闭连接时触发
* */
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
System.out.println("触发关闭websocket连接");
// 移除连接
connections.remove(session.getRemoteAddress().getHostName());
}
@Override
public boolean supportsPartialMessages() {
return super.supportsPartialMessages();
}
/**
* 向连接的客户端发送消息
*
* @author lucky_fd
* @param clientId 客户端标识
* @param message 消息体
**/
public void sendMessage(String clientId, TextMessage message) {
for (String client : connections.keySet()) {
if (client.equals(clientId)) {
try {
WebSocketSession session = connections.get(client);
// 判断连接是否正常
if (session.isOpen()) {
session.sendMessage(message);
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
break;
}
}
}
}
通过消息处理器,在开发中我们就可以实现向指定客户端或所有客户端发送消息,实现相应业务功能。
创建拦截器
拦截器会在握手时触发,可以用来进行权限验证
package com.ci.erp.human.interceptor;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import java.util.Map;
/**
*
* Websocket拦截器类
*
* @author lucky_fd
* @since 2024-01-17
*/
public class WebSocketHandleInterceptor extends HttpSessionHandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
System.out.println("拦截器前置触发");
return super.beforeHandshake(request, response, wsHandler, attributes);
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
System.out.println("拦截器后置触发");
super.afterHandshake(request, response, wsHandler, ex);
}
}
创建前端页面客户端
最后,创建一个简单的HTML页面,用于接收用户输入并显示实时聊天信息。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Spring WebSocket Chat</title>
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js"></script>
</head>
<body>
请输入:<input type="text" id="message" placeholder="Type your message">
<button onclick="sendMessage()">Send</button>
<button onclick="websocketClose()">关闭连接</button>
<div id="chat"></div>
<script>
var socket = null;
if ('WebSocket' in window) {
// 后端服务port为22900
socket = new WebSocket("ws://localhost:22900/websocket/server");
} else if ('MozWebSocket' in window) {
socket = new MozWebSocket("ws://localhost:22900/websocket/server");
} else {
socket = new SockJS("http://localhost:22900/sockjs/server");
}
// 接收消息触发
socket.onmessage = function (event) {
showMessage(event.data);
};
// 创建连接触发
socket.onopen = function (event) {
console.log(event.type);
};
// 连接异常触发
socket.onerror = function (event) {
console.log(event)
};
// 关闭连接触发
socket.onclose = function (closeEvent) {
console.log(closeEvent.reason);
};
//发送消息
function sendMessage() {
if (socket.readyState === socket.OPEN) {
var message = document.getElementById('message').value;
socket.send(message);
console.log("发送成功!");
} else {
console.log("连接失败!");
}
}
function showMessage(message) {
document.getElementById('chat').innerHTML += '<p>' + message + '</p>';
}
function websocketClose() {
socket.close();
console.log("连接关闭");
}
window.close = function () {
socket.onclose();
};
</script>
</body>
</html>
这个页面使用了WebSocket对象来建立连接,并通过onmessage监听收到的消息。通过输入框发送消息,将会在页面上显示。
测试结果:
后端日志:

前端界面:

Java客户端
添加依赖
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.4.0</version>
</dependency>
创建客户端类(继承WebsocketClient)
package com.river.websocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.net.URISyntaxException;
public class MyWebSocketClient extends WebSocketClient {
MyWebSocketClient(String url) throws URISyntaxException {
super(new URI(url));
}
// 建立连接
@Override
public void onOpen(ServerHandshake shake) {
System.out.println(shake.getHttpStatusMessage());
}
// 接收消息
@Override
public void onMessage(String paramString) {
System.out.println(paramString);
}
// 关闭连接
@Override
public void onClose(int paramInt, String paramString, boolean paramBoolean) {
System.out.println("关闭");
}
// 连接异常
@Override
public void onError(Exception e) {
System.out.println("发生错误");
}
}
测试websocket
package com.river.websocket;
import org.java_websocket.enums.ReadyState;
import java.net.URISyntaxException;
/**
* @author lucky_fd
* @date 2024-1-17
*/
public class Client {
public static void main(String[] args) throws URISyntaxException, InterruptedException {
MyWebSocketClient client = new MyWebSocketClient("ws://localhost:22900/websocket/server");
client.connect();
while (client.getReadyState() != ReadyState.OPEN) {
System.out.println("连接状态:" + client.getReadyState());
Thread.sleep(100);
}
client.send("测试数据!");
client.close();
}
}
参考链接:
- 通过注解方式实现websocket:springboot集成websocket持久连接(权限过滤+拦截)
- spring websocket实现前后端通信
Spring WebSocket实现实时通信的详细教程的更多相关文章
- spring Boot+spring Cloud实现微服务详细教程第一篇
前些天项目组的大佬跟我聊,说项目组想从之前的架构上剥离出来公用的模块做微服务的开发,恰好去年的5/6月份在上家公司学习了国内开源的dubbo+zookeeper实现的微服务的架构.自己平时对微服务的设 ...
- Struts2+Spring+Hibernate框架整合总结详细教程
一.SSH三大框架知识总结 Struts 2是Struts的下一代产品,是在 struts 1和WebWork的技术基础上进行了合并的全新的Struts 2框架.其全新的Struts 2的体系结构与S ...
- spring Boot+spring Cloud实现微服务详细教程第二篇
上一篇文章已经说明了一下,关于spring boot创建maven项目的简单步骤,相信很多熟悉Maven+Eclipse作为开发常用工具的朋友们都一目了然,这篇文章主要讲解一下,构建spring bo ...
- spring入门详细教程(五)
前言 本篇紧接着spring入门详细教程(三),建议阅读本篇前,先阅读第一篇,第二篇以及第三篇.链接如下: Spring入门详细教程(一) https://www.cnblogs.com/jichi/ ...
- Spring入门详细教程(四)
前言 本篇紧接着spring入门详细教程(三),建议阅读本篇前,先阅读第一篇,第二篇以及第三篇.链接如下: Spring入门详细教程(一) https://www.cnblogs.com/jichi/ ...
- Spring入门详细教程(三)
前言 本篇紧接着spring入门详细教程(二),建议阅读本篇前,先阅读第一篇和第二篇.链接如下: Spring入门详细教程(一) https://www.cnblogs.com/jichi/p/101 ...
- Spring入门详细教程(二)
前言 本篇紧接着spring入门详细教程(一),建议阅读本篇前,先阅读第一篇.链接如下: Spring入门详细教程(一) https://www.cnblogs.com/jichi/p/1016553 ...
- Spring Boot 与 OAuth2 官方最详细教程
https://mp.weixin.qq.com/s?__biz=MzU0MDEwMjgwNA==&mid=2247484357&idx=1&sn=73e501de8591e6 ...
- Spring WebSocket初探2 (Spring WebSocket入门教程)<转>
See more: Spring WebSocket reference整个例子属于WiseMenuFrameWork的一部分,可以将整个项目Clone下来,如果朋友们有需求,我可以整理一个独立的de ...
- Spring WebSocket初探1 (Spring WebSocket入门教程)<转>
See more: Spring WebSocket reference整个例子属于WiseMenuFrameWork的一部分,可以将整个项目Clone下来,如果朋友们有需求,我可以整理一个独立的de ...
随机推荐
- 数组递增的判断【python实现】
有时候需要对某一组数组的数据进行判断是否 递增 的场景,比如我在开发一些体育动作场景下,某些肢体动作是需要持续朝着垂直方向向上变化,那么z轴的值是会累增的.同理,逆向考虑,递减就是它的对立面. 下面是 ...
- 如何安装和使用 Hugging Face Unity API
Hugging Face Unity API 提供了一个简单易用的接口,允许开发者在自己的 Unity 项目中方便地访问和使用 Hugging Face AI 模型,已集成到 Hugging Face ...
- Java线程池使用浅谈
1. 线程池相关基本概念 任务(Task):任务是线程池中要执行的工作单元.任务可以是实现了 Runnable 接口或 Callable 接口的对象.Runnable 任务没有返回值,而 Callab ...
- 国内申请微软新必应(New Bing)
国内申请微软新必应(New Bing) 本文解决了两个问题: 1 需国外网络环境 2 点击加入候补名单无限返回错误 注册outlook邮箱 https://outlook.live.com/ 一步一步 ...
- CPI 访问需验证的HTTP接口
CPI访问外围系统接口时,有时需要先访问验证接口,获取AccessToken或得到账密,然后访问具体接口时,将获取到的验证结果传入具体接口进行访问 1.OAuth2.0 AccessToken方式 1 ...
- 【JAVA基础】事务管理
@Transactional 注解的属性介绍 @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.cl ...
- .net 温故知新【17】:Asp.Net Core WebAPI 中间件
一.前言 到这篇文章为止,关于.NET "温故知新"系列的基础知识就完结了,从这一系列的系统回顾和再学习,对于.NET core.ASP.NET CORE又有了一个新的认识. 不光 ...
- rem布局,使用手机端,pc大屏——亲测好用
1 var docEl = document.documentElement, 2 //当设备的方向变化(设备横向持或纵向持)此事件被触发.绑定此事件时, 3 //注意现在当浏览器不支持orienta ...
- 云网络智慧课堂-Qt程序代码开发规范
序言: 编程规范可以提升代码可读性,提高可维护性. 目录: 一.命名规范 二.内存管理规范 三.函数方法规范 四.控制语句规范 五.注释规范 六.排版规范 七.版本管理规范 八.界面编程 词义解释:强 ...
- P1765
和那道题一样,这次用的getchar,结果对了可是洛谷评测WA了,换成scanf单个字符,结果还是WA了,换成直接getline读入整个字符串就对了. 可见读入单个字符的方式有可能出现各种小错,尤其是 ...