SpringBoot+WebSocket实现消息推送功能
作者:剑圣无痕 发布时间:2021-11-15 12:16:18
背景
项目中经常会用到消息推送功能,关于推送技术的实现,我们通常会联想到轮询、comet长连接技术,虽然这些技术能够实现,但是需要反复连接,对于服务资源消耗过大,随着技术的发展,HtML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。本文将介绍如何采用websocket实现消息推送。
WebSocket简介
WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。浏览器和服务器仅需一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
协议原理
Websocket协议基于Http协议,针对Http协议进行了相关的改善,且Websocket协议也需要建立TCP连接来实现数据传输,具体实现如下图:
说明:
客户端发起http请求,经过3次握手后,建立起TCP连接;http请求里存放WebSocket支持的版本号等信息,如:Upgrade、Connection、WebSocket-Version等。
服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据
客户端收到连接成功的消息后,开始借助于TCP传输信道进行全双工通信.
WebSocket与HTTP协议的区别
相同点:都是一样基于TCP的,都是可靠性传输协议。都是应用层协议。
不同点:
WebSocket是双向通信协议,可以双向发送或接受信息,而HTTP是单向协议
WebSocket需要浏览器和服务器握手进行建立连接的,而http是浏览器发起向服务器的连接。
WebSocket特点
建立在TCP协议之上,服务器端的实现比较容易。
数据格式比较轻量,性能开销小,通信高效。
支持多种数据格式,可以发送文本、二进制数据。
客户端可以与任意服务器通信,无同源限制。
应用场景
即时聊天通信
在线协同编辑/编辑
实时数据流的拉取与推送
实时地图位置
系统集成Websocket
jar包引入
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
Websocket配置
@Configuration
public class WebSocketConfig
{
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
具体实现
@ServerEndpoint(value="/websocket/{uid}")
@Component
public class WebSocketServer
{
private Logger logger = LoggerFactory.getLogger(WebSocketServer.class);
private static final AtomicInteger onlineCount = new AtomicInteger(0);
private static CopyOnWriteArraySet<Session> sessionSet = new CopyOnWriteArraySet<Session>();
@OnOpen
public void onOpen(Session session,@PathParam("uid") String uid)
{
logger.info("open message uid:{}",uid);
sessionSet.add(session);
onlineCount.incrementAndGet();
logger.info("窗口开始监听uid:{},当前在线人数:{}",uid,onlineCount);
}
@OnClose
public void onClose(Session session)
{
String sessionId=session.getId();
logger.info("sessionid:{} close",sessionId);
sessionSet.remove(this);
int count=onlineCount.decrementAndGet();
logger.info("有一连接关闭!当前在线人数为:{}",count);
}
@OnError
public void onError(Session session, Throwable error)
{
logger.error("消息发生错误:{},Session ID: {}",error.getMessage(),session.getId());
}
public void batchSendMesasge(String uid,String message) throws IOException
{
logger.info("推送消息到窗口:{},推送内容:{}",uid,message);
for(Session session:sessionSet){
sendMessage(session, message);
}
}
public void sendMessage(Session session, String message) throws IOException {
if(session!=null)
{
synchronized (session) {
session.getBasicRemote().sendText(message);
}
}
}
}
说明: @OnOpen :当有新的WebSocket连接进入时调用 @OnClose:当有WebSocket连接关闭时调用 @OnError :当有WebSocket抛出异常时调用 @OnMessage:当接收到字符串消息时,对该方法进行回调
测试示例
@Controller
public class WebScoketController
{
@Autowired
private WebSocketServer webSocketServer;
@ResponseBody
@RequestMapping("/sendMessage")
public String batchMessage(String uid,String message)
{
Map<String, String> map =new HashMap<String, String>();
try
{
map.put("code", "200");
webSocketServer.batchSendMesasge(uid,message);
}
catch (Exception e)
{
map.put("code", "-1");
map.put("message", e.getMessage());
}
return JSON.toJSONString(map);
}
@GetMapping("/enter")
public String enter()
{
return "webscoketTest.html";
}
}
页面请求websocket
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>websocket test</title>
<script type="text/javascript">
if ("WebSocket" in window)
{
console.log("您的浏览器支持 WebSocket!");
var ws = new WebSocket("ws://127.0.0.1:9092/websocket/1234");
console.log('ws连接状态:' + ws.readyState);
//打开
ws.onopen = function()
{
ws.send("message test");
console.log("mesage sending");
};
//发送消息
ws.onmessage = function (evt)
{
var received_msg = evt.data;
alert(received_msg);
};
//关闭
ws.onclose = function()
{
// 关闭 websocket
console.log("socket is close");
};
}
else
{
console.log("您的浏览器不支持 WebSocket!");
}
</script>
</head>
</html>
测试效果
启动程序:运行http://localhost:9092/enter 进入页面开启websocket。
用户发送消息:http://localhost:9092/sendMessage?uid=1235&message=this%20is%20message1
执行的结果如下:
open message uid:1234
[nio-9092-exec-2] c.s.f.w.controller.WebSocketServer: 窗口开始监听uid:1234,当前在线人数:1
[nio-9092-exec-5] c.s.f.w.controller.WebSocketServer: 推送消息到窗口:1234,推送内容:this is message
来源:https://juejin.cn/post/7129534763789975588


猜你喜欢
- 近期做简单的新闻客户端界面使用到了Jsoup获取,使用起来特别方便,这也是被我一个学长称为学android网络必学的一个东西,在此也是分享一
- 这篇文章主要介绍了Java并发编程预防死锁过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可
- java 弹幕小游戏的最初版本,供大家参考,具体内容如下最近在学习javaSE,根据b站视频老师的讲解,也参考了他的代码,做了一个弹幕小游戏
- 第一步:官网(或跟硬件开发WMI的人沟通你需要的接口和参数定义,如果是和硬件开发的人协定WMI接口,直接看第二步)查找你需要的WMI信息;举
- 网站中为了防止恶意获取验证短信、验证邮箱,都会在点击获取验证码的按钮上做个倒计时的效果,如何实现这个效果,具体内容如下效果图: 代
- java 中的instanceof 运算符是用来在运行时指出对象是否是特定类的一个实例。instanceof通过返回一个布尔值来指出,这个对
- 企业级项目开发中都会有文件、图片、视频等文件上传并能够访问的场景,对于初学者Demo可能会直接存储在应用服务器上;对于传统项目可能会单独搭建
- 干Java这么久,一直在做WEB相关的项目,一些基础类差不多都已经忘记。经常想得捡起,但总是因为一些原因,不能如愿。其实不是没有时间,只是有
- @Cacheable在同一类中方法调用无效上述图片中,同一个类中genLiveBullets()方法调用同类中的queryLiveByRoo
- 定义枚举类型时本质上就是在定义一个类,只不过很多细节由编译器帮您补齐了,所以某些程度上,enum关键字的 作用就像是class或interf
- 有时候如果想让我们的应用在桌面上创建多个快捷方式,我们可以在Manifest.xml文件中对相应的activity进行声明。<appl
- 最近JAVA和JSwing上手练习了一下贪吃蛇,供大家参考,具体内容如下欢迎交流和加入新的内容用到了JSwing,下面是一些具体的思路实现&
- 我们知道,多线程是Android开发中必现的场景,很多原生API和开源项目都有多线程的内容,这里简单总结和探讨一下常见的多线程切换方式。我们
- 首先来说一说该指南针的实现思路:程序先准备一张指南针图片,该图片上方向指针指向北方。接下来开发一个检测方向的传感器,程序检测到手机顶部绕Z轴
- 目录前言hibernate-validator基本使用引入依赖编写需要验证对象验证对象属性是否符合要求验证规则空/非空验证bool时间数学字
- 前言最近公司要把百度地图集成的项目中,于是我就研究了一天百度地图的SDK,当前的版本:Android SDK v3.0.0 。 虽然百度地图
- 在使用AndroidNDK开发的时候有个事情是很烦人的,那就是创建本地代码文件夹,生成本地代码文件和创建本地代码的编译文件。特别是实现本地方
- 一、数组的基本用法1.什么是数组数组:存储一组相同数据类型的数据的集合。2.定义数组 int[] :int类型数组 do
- Android当道,现在学习Android开发还晚吗?写下这个问题的时间是–2014年6月15号,我会回答:不晚,Android至少还能在活
- Spring读取配置文件Document在XmlBeanDefinitionReader.doLoadBeanDefinitions(Inp