假设有这样一个场景:服务端的资源经常在更新,客户端需要尽量及时地了解到这些更新发生后展示给用户,如果是 HTTP 1.1,通常会开启 ajax 请求询问服务端是否有更新,通过定时器反复轮询服务端响应的资源是否有更新。

在长时间不更新的情况下,反复地去询问会对服务器造成很大的压力,对网络也有很大的消耗,如果定时的时间比较大,服务端有更新的话,客户端可能需要等待定时器达到以后才能获知,这个信息也不能很及时地获取到。

而有了 WebSocket 协议,就能很好地解决这些问题,WebSocket 可以反向通知的,通常向服务端订阅一类消息,服务端发现这类消息有更新就会不停地通知客户端。

WebSocket 简介

WebSocket 协议是基于 TCP 的一种新的网络协议,它实现了浏览器与服务器全双工(full-duplex)通信—允许服务器主动发送信息给客户端,这样就可以实现从客户端发送消息到服务器,而服务器又可以转发消息到客户端,这样就能够实现客户端之间的交互。对于 WebSocket 的开发,Spring 也提供了良好的支持,目前很多浏览器已经实现了 WebSocket 协议,但是依旧存在着很多浏览器没有实现该协议,为了兼容那些没有实现该协议的浏览器,往往还需要通过 STOMP 协议来完成这些兼容。

下面我们在 Spring Boot 中集成 WebSocket 来实现服务端推送消息到客户端。

Spring Boot 集成 WebSocket

首先创建一个 Spring Boot 项目,然后在 pom.xml 加入如下依赖集成 WebSocket:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

开启配置

接下来在 config 包下创建一个 WebSocket 配置类 WebSocketConfiguration,在配置类上加入注解 @EnableWebSocket,表明开启 WebSocket,内部实例化 ServerEndpointExporter 的 Bean,该 Bean 会自动注册 @ServerEndpoint 注解声明的端点,代码如下:

@Configuration
@EnableWebSocket
public class WebSocketConfiguration { @Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}

编写端点服务类

接下来使用 @ServerEndpoint 定义一个端点服务类,在端点服务类中,可以定义 WebSocket 的打开、关闭、错误和发送消息的方法,具体代码如下所示:

@ServerEndpoint("/websocket/{userId}")
@Component
public class WebSocketServer { private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class); /**
* 当前在线连接数
*/
private static AtomicInteger onlineCount = new AtomicInteger(0); /**
* 用来存放每个客户端对应的 WebSocketServer 对象
*/
private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>(); /**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session; /**
* 接收 userId
*/
private String userId = ""; /**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.session = session;
this.userId = userId;
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
webSocketMap.put(userId, this);
} else {
webSocketMap.put(userId, this);
addOnlineCount();
}
log.info("用户连接:" + userId + ",当前在线人数为:" + getOnlineCount());
try {
sendMessage("连接成功!");
} catch (IOException e) {
log.error("用户:" + userId + ",网络异常!!!!!!");
}
} /**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
subOnlineCount();
}
log.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount());
} /**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("用户消息:" + userId + ",报文:" + message);
if (!StringUtils.isEmpty(message)) {
try {
JSONObject jsonObject = JSON.parseObject(message);
jsonObject.put("fromUserId", this.userId);
String toUserId = jsonObject.getString("toUserId");
if (!StringUtils.isEmpty(toUserId) && webSocketMap.containsKey(toUserId)) {
webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
} else {
log.error("请求的 userId:" + toUserId + "不在该服务器上");
}
} catch (Exception e) {
e.printStackTrace();
}
}
} /**
* 发生错误时调用
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用户错误:" + this.userId + ",原因:" + error.getMessage());
error.printStackTrace();
} /**
* 实现服务器主动推送
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
} public static synchronized AtomicInteger getOnlineCount() {
return onlineCount;
} public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount.getAndIncrement();
} public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount.getAndDecrement();
}
}

其中,@ServerEndpoint("/websocket/{userId}")表示让 Spring 创建 WebSocket 的服务端点,其中请求地址是 /websocket/{userId}

另外 WebSocket 一共有四个事件,分别对应 JSR-356 定义的 @OnOpen、@OnMessage、@OnClose、@OnError 注解。

  • @OnOpen:标注客户端打开 WebSocket 服务端点调用方法
  • @OnClose:标注客户端关闭 WebSocket 服务端点调用方法
  • @OnMessage:标注客户端发送消息,WebSocket 服务端点调用方法
  • @OnError:标注客户端请求 WebSocket 服务端点发生异常调用方法

接下来启动项目,使用 WebSocket 在线测试工具(http://www.easyswoole.com/wstool.html)进行测试,有能力的也可以自己写个 html 测试。

打开网页后,在服务地址中输入ws://127.0.0.1:8080/websocket/wupx,点击开启连接按钮,消息记录中会多一条由服务器端发送的连接成功!记录。

接下来再打开一个网页,服务地址中输入ws://127.0.0.1:8080/websocket/huxy,点击开启连接按钮,然后回到第一次打开的网页在消息框中输入{"toUserId":"huxy","message":"i love you"},点击发送到服务端,第二个网页中会收到服务端推送的消息{"fromUserId":"wupx","message":"i love you","toUserId":"huxy"}

同样,项目的日志中也会有相应的日志:

2020-06-30 12:40:48.894  INFO 78908 --- [nio-8080-exec-1] com.wupx.server.WebSocketServer          : 用户连接:wupx,当前在线人数为:1
2020-06-30 12:40:58.073 INFO 78908 --- [nio-8080-exec-2] com.wupx.server.WebSocketServer : 用户连接:huxy,当前在线人数为:2
2020-06-30 12:41:05.870 INFO 78908 --- [nio-8080-exec-3] com.wupx.server.WebSocketServer : 用户消息:wupx,报文:{"toUserId":"huxy","message":"i love you"}

总结

本文简单地介绍了 Spring Boot 集成 WebSocket 实现服务端主动推送消息到客户端,是不是十分简单呢?大家可以自己也写个 demo 试试!

本文的完整代码在 https://github.com/wupeixuan/SpringBoot-Learnwebsocket 目录下。

最好的关系就是互相成就,大家的点赞、在看、转发、留言就是我创作的最大动力。

参考

https://github.com/wupeixuan/SpringBoot-Learn

《深入浅出Spring Boot 2.x》

Spring Boot 集成 WebSocket 实现服务端推送消息到客户端的更多相关文章

  1. Netty实现一个简单聊天系统(点对点及服务端推送)

    Netty是一个基于NIO,异步的,事件驱动的网络通信框架.由于使用Java提供 的NIO包中的API开发网络服务器代码量大,复杂,难保证稳定性.netty这类的网络框架应运而生.通过使用netty框 ...

  2. spring集成webSocket实现服务端向前端推送消息

    原文:https://blog.csdn.net/ya_nuo/article/details/79612158 spring集成webSocket实现服务端向前端推送消息   1.前端连接webso ...

  3. spring boot 集成 websocket 实现消息主动推送

    spring boot 集成 websocket 实现消息主动 前言 http协议是无状态协议,每次请求都不知道前面发生了什么,而且只可以由浏览器端请求服务器端,而不能由服务器去主动通知浏览器端,是单 ...

  4. 【websocket】spring boot 集成 websocket 的四种方式

    集成 websocket 的四种方案 1. 原生注解 pom.xml <dependency> <groupId>org.springframework.boot</gr ...

  5. C# 服务端推送,十步十分钟,从注册到推送成功

    目标 展示 C# 服务端集成极光推送的步骤,多图少字,有图有真相. 使用极光推送, C# 服务端推送到 Demo App,Android 手机收到推送,整理为十个步骤,使用十分钟左右,完成从注册账号到 ...

  6. 一文了解服务端推送(含JS代码示例)

    常用的服务端推送技术,包括轮询.长轮询.websocket.server-sent-event(SSE) 传统的HTTP请求是由客户端发送一个request,服务端返回对应response,所以当服务 ...

  7. [译]servlet3.0与non-blocking服务端推送技术

    Non-blocking(NIO)Server Push and Servlet 3 在我的前一篇文章写道如何期待成熟的使用node.js.假定有一个框架,基于该框架,开发者只需要定义协议及相关的ha ...

  8. 升级NGINX支持HTTP/2服务端推送

    内容概览 NGINX从1.13.9版本开始支持HTTP/2服务端推送,上周找时间升级了下NGINX,在博客上试验新的特性. 升级工作主要包括: 升级NGINX 修改NGINX配置 修改wordpres ...

  9. mqtt协议实现 java服务端推送功能(三)项目中给多个用户推送功能

    接着上一篇说,上一篇的TOPIC是写死的,然而在实际项目中要给不同用户 也就是不同的topic进行推送 所以要写活 package com.fh.controller.information.push ...

随机推荐

  1. Java 从入门到进阶之路(二十六)

    在之前的文章我们介绍了一下 Java 中的  集合框架中的Collection 的子接口 List,本章我们来看一下 Java 集合框架中的Collection 的子接口 Queue. 在之前我们讲 ...

  2. AnalyticDB实现和特点浅析

    目录 AnalyticDB介绍与背景 AnalyticDB详细解析 架构设计 数据分区 读写分离和读写流程 其他特性介绍 混合(列-行)存储引擎 索引 小结 本篇主要是根据AnalyticDB的论文, ...

  3. redis 链接数满了

    服务器上可以设置timeout参数,这样可以将限制的连接自动释放掉.

  4. .net core Web API参数绑定规则

    参数推理绑定 先从一个问题说起,微信小程序按照WebAPI定义的参数传递,Get请求服务器端可以正常接收到参数,但是Post请求取不到. Web API代码(.netcore 3.1)如下: [Htt ...

  5. 带你认识网站图片img懒加载和预加载的区别

    懒加载 什么是懒加载? 懒加载也就是延迟加载.当访问一个页面的时候,先把img元素或是其他元素的背景图片路径替换成一张大小为1*1px图片的路径(这样就只需请求一次,俗称占位图),只有当图片出现在浏览 ...

  6. 请解释ASP. NET中的web页面与隐藏类之间的关系

    请解释ASP.NET中的web页面与其隐藏类之间的关系 其实页面与其隐藏类之间就是一个部分类的关系,你在页面上放一个一个的控件就是在这个类中定义一个一个的属性, 因为是同一个类的部分类的关系,所以隐藏 ...

  7. MySQL 事务 异常 事务隔离的级别

    MySQL 事务 异常 事务隔离的级别   事务 在你操作数据库的同时,有可能其他用户还会不断地对数据进行增删改查操作.为了避免并行进行时出现混乱,就产生了"事务".事务就是要保证 ...

  8. BZOJ3573 米特运输 题解

    题目 米特是D星球上一种非常神秘的物质,蕴含着巨大的能量.在以米特为主要能源的D星上,这种米特能源的运输和储存一直是一个大问题.D星上有N个城市,我们将其顺序编号为1到N,1号城市为首都.这N个城市由 ...

  9. Uni-app实战项目注意事项

    注意: (1)本地开启端口 App running at: Local: http://localhost:8080/ Network: http://192.168.31.43:8080/ 后台人员 ...

  10. XSS与CSRF定义

    一. CSRF 1. CSRF的基本概念 跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通 ...