简介

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();
}
}

参考链接:

Spring WebSocket实现实时通信的详细教程的更多相关文章

  1. spring Boot+spring Cloud实现微服务详细教程第一篇

    前些天项目组的大佬跟我聊,说项目组想从之前的架构上剥离出来公用的模块做微服务的开发,恰好去年的5/6月份在上家公司学习了国内开源的dubbo+zookeeper实现的微服务的架构.自己平时对微服务的设 ...

  2. Struts2+Spring+Hibernate框架整合总结详细教程

    一.SSH三大框架知识总结 Struts 2是Struts的下一代产品,是在 struts 1和WebWork的技术基础上进行了合并的全新的Struts 2框架.其全新的Struts 2的体系结构与S ...

  3. spring Boot+spring Cloud实现微服务详细教程第二篇

    上一篇文章已经说明了一下,关于spring boot创建maven项目的简单步骤,相信很多熟悉Maven+Eclipse作为开发常用工具的朋友们都一目了然,这篇文章主要讲解一下,构建spring bo ...

  4. spring入门详细教程(五)

    前言 本篇紧接着spring入门详细教程(三),建议阅读本篇前,先阅读第一篇,第二篇以及第三篇.链接如下: Spring入门详细教程(一) https://www.cnblogs.com/jichi/ ...

  5. Spring入门详细教程(四)

    前言 本篇紧接着spring入门详细教程(三),建议阅读本篇前,先阅读第一篇,第二篇以及第三篇.链接如下: Spring入门详细教程(一) https://www.cnblogs.com/jichi/ ...

  6. Spring入门详细教程(三)

    前言 本篇紧接着spring入门详细教程(二),建议阅读本篇前,先阅读第一篇和第二篇.链接如下: Spring入门详细教程(一) https://www.cnblogs.com/jichi/p/101 ...

  7. Spring入门详细教程(二)

    前言 本篇紧接着spring入门详细教程(一),建议阅读本篇前,先阅读第一篇.链接如下: Spring入门详细教程(一) https://www.cnblogs.com/jichi/p/1016553 ...

  8. Spring Boot 与 OAuth2 官方最详细教程

    https://mp.weixin.qq.com/s?__biz=MzU0MDEwMjgwNA==&mid=2247484357&idx=1&sn=73e501de8591e6 ...

  9. Spring WebSocket初探2 (Spring WebSocket入门教程)<转>

    See more: Spring WebSocket reference整个例子属于WiseMenuFrameWork的一部分,可以将整个项目Clone下来,如果朋友们有需求,我可以整理一个独立的de ...

  10. Spring WebSocket初探1 (Spring WebSocket入门教程)<转>

    See more: Spring WebSocket reference整个例子属于WiseMenuFrameWork的一部分,可以将整个项目Clone下来,如果朋友们有需求,我可以整理一个独立的de ...

随机推荐

  1. 2021InfoComm|钉钉会议 Rooms 的 "全场景" 智能化解决方案

    InfoComm China 是亚太地区规模盛大的专业视听和集成体验解决方案商贸展会,提供前沿革新的产品和一系列高价值的技术展示. 在疫情期间,钉钉音视频支持了全国人民在线办公.在家上课,单日在线会议 ...

  2. 遇到 Request header is too large,你是如何解决的?

    看到群里有小伙伴问,这个异常要怎么解决: java.lang.IllegalArgumentException: Request header is too large 异常原因 根据Exceptio ...

  3. 【OpenSSL】​Visual Studio 2019配置OpenSSL 3.0开发环境

    OpenSSL从1.0.2版本升级为3.0.3版本后,需要对代码进行重构.如果不可用的代码太多,需要重新开一个项目.重新配置开发环境. [第一步]登录http://slproweb.com/,下载Wi ...

  4. js判断undefined

    if (item2.shifoushiyong === 1) { if( typeof(item2.koufen) == "undefined" ) { item2.koufen ...

  5. 08_N叉树的层序遍历

    N叉树的层序遍历 给定一个 N 叉树,返回其节点值的层序遍历.(即从左到右,逐层遍历). 树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例). 示例 1: 输入:root = [ ...

  6. java基础-java面向对象01-day08

    1. 一个简单的类 认识类 成员变量 类方法 public class Person { //类的成员变量 int age; String name; double height; double we ...

  7. python3使用json、pickle和sqlite3持久化存储字典对象

    技术背景 在各种python的项目中,我们时常要持久化的在系统中存储各式各样的python的数据结构,常用的比如字典等.尤其是在云服务类型中的python项目中,要持久化或者临时的在缓存中储存一些用户 ...

  8. js - body的滚动事件的坑

    文章来源 : https://www.cnblogs.com/Zting00/p/7497629.html 踩过些坑,得到的结论,不一定精确 1. body的滚动条,刷新页面的时候不会回到顶部.其他d ...

  9. 战略设计- DDD

    随着系统的增长,它会变得越来越复杂,当我们无法通过分析对象来理解系统的时候,就需要掌握一些操纵和理解大模型的技术了.本文将介绍一些原则.遵循这些原则,就可以对非常复杂的领域进行建模.大部分这样的决策都 ...

  10. Qt5.9 UI设计(四)——布局设计及自定义界面

    前言 前面我们已经创建了mainwindow ControlTabWidget ControlTreeWidget maintitlebar 4个UI几面,我们需要将其他三个UI放置到mainwind ...