javaWEB消息推送之 WebSocket和SseEmitter

这篇具有很好参考价值的文章主要介绍了javaWEB消息推送之 WebSocket和SseEmitter。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

用途

  • 实时获取服务端的最新数据
  • 查看调度任务的进度和执行状态
  • 用户感知:修改数据后,相关用户收到信息
  • 提升用户体验:耗时业务异步处理(Excel导入导出,复杂计算)

前端轮询

这种方式实现简单,前端通过setInterval定时去请求接口来获取最新的数据,当实时性要求不高,更新频率低的情况下可以使用这种方式。但是当实时性很高的时候,我们的请求会很频繁,服务器的消耗非常大,而且每次请求的时候服务端的数据可能还没有改变,导致很多请求都是没有意义的。

 

javascript

复制代码

    setInterval(function () {             // 请求接口操作             // 。。。         },         3000     );

webSocket

WebSocket是基于TCP协议的,它是全双工通信的,服务端可以向客户端发送信息,客户端同样可以向服务器发送指令,常用于聊天应用中。

pom.xml

SpringBoot提供了websocket的starter

 

xml

复制代码

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

config类

注入ServerEndpointExporter,这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint

 

typescript

复制代码

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

server类

创建一个服务类:

  • 加上@ServerEndpoint注解,设置WebSocket连接点的服务地址。
  • 创建AtomicInteger用于记录连接数
  • 创建ConcurrentHashMap用于存放连接信息
  • @OnOpen注解表明该方法在建立连接后调用
  • @OnClose注解表明该方法在断开连接后调用
  • @OnError注解表明该方法在连接异常调用
  • @OnMessage注解表明该方法在收到客户端消息后调用
  • 创建推送信息的方法
  • 创建移除连接的方法
 

typescript

复制代码

@ServerEndpoint("/websocket/{userId}") @Component public class WebSocketServer {     private final static Logger logger = LoggerFactory.getLogger(WebSocketServer.class);     /**      * 当前连接数      */     private static AtomicInteger count = new AtomicInteger(0);     /**      * 使用map对象,便于根据userId来获取对应的WebSocket,或者放redis里面      */     private static Map<String, WebSocketServer> websocketMap = new ConcurrentHashMap<>();     /**      * 与某个客户端的连接会话,需要通过它来给客户端发送数据      */     private Session session;     /**      * 对应的用户ID      */     private String userId = "";     /**      * 连接建立成功调用的方法      */     @OnOpen     public void onOpen(Session session, @PathParam("userId") String userId) {         try {             this.session = session;             this.userId = userId;             websocketMap.put(userId, this);             // 数量+1             count.getAndIncrement();             logger.info("websocket 新连接:{}", userId);         } catch (Exception e) {             logger.error("websocket 新建连接 IO异常");         }     }     /**      * 连接关闭调用的方法      */     @OnClose     public void onClose() {         // 删除         websocketMap.remove(this.userId);         // 数量-1         count.getAndDecrement();         logger.info("close websocket : {}", this.userId);     }     /**      * 收到客户端消息后调用的方法      *      * @param message 客户端发送过来的消息      */     @OnMessage     public void onMessage(String message) {         logger.info("来自客户端{}的消息:{}", this.userId, message);     }     @OnError     public void onError(Throwable error) {         logger.info("websocket 发生错误,移除当前websocket:{},err:{}", this.userId, error.getMessage());         websocketMap.remove(this.userId);         // 数量-1         count.getAndDecrement();     }     /**      * 发送消息 (异步发送)      *      * @param message 消息主题      */     private void sendMessage(String message) {         this.session.getAsyncRemote().sendText(message);     }     /**      * 向指定用户发送信息      *      * @param userId 用户id      * @param wsInfo 信息      */     public static void sendInfo(String userId, String wsInfo) {         if (websocketMap.containsKey(userId)) {             websocketMap.get(userId).sendMessage(wsInfo);         }     }     /**      * 群发消息      */     public static void batchSendInfo(String wsInfo, List<String> ids) {         ids.forEach(userId -> sendInfo(userId, wsInfo));     }     /**      * 群发所有人      */     public static void batchSendInfo(String wsInfo) {         websocketMap.forEach((k, v) -> v.sendMessage(wsInfo));     }     /**      * 获取当前连接信息      */     public static List<String> getIds() {         return new ArrayList<>(websocketMap.keySet());     }     /**      * 获取当前连接数量      */     public static int getUserCount() {         return count.intValue();     } }

测试接口

 

less

复制代码

@RestController @RequestMapping("/ws") public class WebSocketController {     @GetMapping("/push/{message}")     public ResponseEntity<String> push(@PathVariable(name = "message") String message) {         WebSocketServer.batchSendInfo(message);         return ResponseEntity.ok("WebSocket 推送消息给所有人");     } }

html

resources/static下创建ws.html,将WebSocket的地址设为服务类中@ServerEndpoint注解所配置的地址

 

xml

复制代码

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>WebSocket</title> </head> <body> <div id="message"></div> </body> <script>     let websocket = null;     // 用时间戳模拟登录用户     const username = new Date().getTime();     // alert(username)     //判断当前浏览器是否支持WebSocket     if ('WebSocket' in window) {         console.log("浏览器支持Websocket");         websocket = new WebSocket('ws://localhost:8080/websocket/' + username);     } else {         alert('当前浏览器 不支持 websocket');     }     //连接发生错误的回调方法     websocket.onerror = function () {         setMessageInnerHTML("WebSocket连接发生错误");     };     //连接成功建立的回调方法     websocket.onopen = function () {         setMessageInnerHTML("WebSocket连接成功");     };     //接收到消息的回调方法     websocket.onmessage = function (event) {         setMessageInnerHTML(event.data);     };     //连接关闭的回调方法     websocket.onclose = function () {         setMessageInnerHTML("WebSocket连接关闭");     };     //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。     window.onbeforeunload = function () {         closeWebSocket();     };     //关闭WebSocket连接     function closeWebSocket() {         websocket.close();     }     //将消息显示在网页上     function setMessageInnerHTML(innerHTML) {         document.getElementById('message').innerHTML += innerHTML + '<br/>';     } </script> </html>

测试

启动项目,访问http://localhost:8080/ws.html,开启连接。调用消息推送接口http://localhost:8080/ws/push/hello,查看网页显示信息。

SseEmitter

SseEmitter是SpringMVC(4.2+)提供的一种技术,它是基于Http协议的,相比WebSocket,它更轻量,但是它只能从服务端向客户端单向发送信息。在SpringBoot中我们无需引用其他jar就可以使用。

创建服务类

  • 创建AtomicInteger用于记录连接数
  • 创建ConcurrentHashMap用于存放连接信息
  • 建立连接:创建并返回一个带有超时时间的SseEmitter给前端。超时间设为0表示永不过期
  • 设置连接结束的回调方法completionCallBack
  • 设置连接超时的回调方法timeoutCallBack
  • 设置连接异常的回调方法errorCallBack
  • 创建推送信息的方法SseEmitter.send()
  • 创建移除连接的方法
 

scss

复制代码

public class SseEmitterServer {     private static final Logger logger = LoggerFactory.getLogger(SseEmitterServer.class);     /**      * 当前连接数      */     private static AtomicInteger count = new AtomicInteger(0);     /**      * 使用map对象,便于根据userId来获取对应的SseEmitter,或者放redis里面      */     private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();     /**      * 创建用户连接并返回 SseEmitter      *      * @param userId 用户ID      * @return SseEmitter      */     public static SseEmitter connect(String userId) {         // 设置超时时间,0表示不过期。默认30秒,超过时间未完成会抛出异常:AsyncRequestTimeoutException         SseEmitter sseEmitter = new SseEmitter(0L);         // 注册回调         sseEmitter.onCompletion(completionCallBack(userId));         sseEmitter.onError(errorCallBack(userId));         sseEmitter.onTimeout(timeoutCallBack(userId));         sseEmitterMap.put(userId, sseEmitter);         // 数量+1         count.getAndIncrement();         logger.info("创建新的sse连接,当前用户:{}", userId);         return sseEmitter;     }     /**      * 给指定用户发送信息      */     public static void sendMessage(String userId, String message) {         if (sseEmitterMap.containsKey(userId)) {             try {                 // sseEmitterMap.get(userId).send(message, MediaType.APPLICATION_JSON);                 sseEmitterMap.get(userId).send(message);             } catch (IOException e) {                 logger.error("用户[{}]推送异常:{}", userId, e.getMessage());                 removeUser(userId);             }         }     }     /**      * 群发消息      */     public static void batchSendMessage(String wsInfo, List<String> ids) {         ids.forEach(userId -> sendMessage(wsInfo, userId));     }     /**      * 群发所有人      */     public static void batchSendMessage(String wsInfo) {         sseEmitterMap.forEach((k, v) -> {             try {                 v.send(wsInfo, MediaType.APPLICATION_JSON);             } catch (IOException e) {                 logger.error("用户[{}]推送异常:{}", k, e.getMessage());                 removeUser(k);             }         });     }     /**      * 移除用户连接      */     public static void removeUser(String userId) {         sseEmitterMap.remove(userId);         // 数量-1         count.getAndDecrement();         logger.info("移除用户:{}", userId);     }     /**      * 获取当前连接信息      */     public static List<String> getIds() {         return new ArrayList<>(sseEmitterMap.keySet());     }     /**      * 获取当前连接数量      */     public static int getUserCount() {         return count.intValue();     }     private static Runnable completionCallBack(String userId) {         return () -> {             logger.info("结束连接:{}", userId);             removeUser(userId);         };     }     private static Runnable timeoutCallBack(String userId) {         return () -> {             logger.info("连接超时:{}", userId);             removeUser(userId);         };     }     private static Consumer<Throwable> errorCallBack(String userId) {         return throwable -> {             logger.info("连接异常:{}", userId);             removeUser(userId);         };     } }

测试接口

 

less

复制代码

@RestController @RequestMapping("/sse") public class SseEmitterController {     /**      * 用于创建连接      */     @GetMapping("/connect/{userId}")     public SseEmitter connect(@PathVariable String userId) {         return SseEmitterServer.connect(userId);     }     @GetMapping("/push/{message}")     public ResponseEntity<String> push(@PathVariable(name = "message") String message) {         SseEmitterServer.batchSendMessage(message);         return ResponseEntity.ok("WebSocket 推送消息给所有人");     } }

html

resources/static下创建ws.html,将EventSource的地址设为创建连接的地址

 

xml

复制代码

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>SseEmitter</title> </head> <body> <button onclick="closeSse()">关闭连接</button> <div id="message"></div> </body> <script>     let source = null;     // 用时间戳模拟登录用户     const userId = new Date().getTime();     if (!!window.EventSource) {         // 建立连接         source = new EventSource('http://localhost:8080/sse/connect/' + userId);         /**          * 连接一旦建立,就会触发open事件          * 另一种写法:source.onopen = function (event) {}          */         source.addEventListener('open', function (e) {             setMessageInnerHTML("建立连接。。。");         }, false);         /**          * 客户端收到服务器发来的数据          * 另一种写法:source.onmessage = function (event) {}          */         source.addEventListener('message', function (e) {             setMessageInnerHTML(e.data);         });         /**          * 如果发生通信错误(比如连接中断),就会触发error事件          * 或者:          * 另一种写法:source.onerror = function (event) {}          */         source.addEventListener('error', function (e) {             if (e.readyState === EventSource.CLOSED) {                 setMessageInnerHTML("连接关闭");             } else {                 console.log(e);             }         }, false);     } else {         setMessageInnerHTML("你的浏览器不支持SSE");     }     // 监听窗口关闭事件,主动去关闭sse连接,如果服务端设置永不过期,浏览器关闭后手动清理服务端数据     window.onbeforeunload = function () {         closeSse();     };     // 关闭Sse连接     function closeSse() {         source.close();         const httpRequest = new XMLHttpRequest();         httpRequest.open('GET', 'http://localhost:8080/sse/close/' + userId, true);         httpRequest.send();         console.log("close");     }     // 将消息显示在网页上     function setMessageInnerHTML(innerHTML) {         document.getElementById('message').innerHTML += innerHTML + '<br/>';     } </script> </html>

测试

启动项目,访问网页http://localhost:8080/sse.html建立连接。调用发送信息接口http://localhost:8080/sse/push/hello,查看网页显示信息。文章来源地址https://www.toymoban.com/news/detail-819507.html

到了这里,关于javaWEB消息推送之 WebSocket和SseEmitter的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Vue 项目中使用WebSocket 消息推送

    1.这是我在后台管理项目中使用到的,主要的作用是搞一个消息提醒的功能。 2.主要有右上角的提示和有下角的消息弹框。 3.主要实现的功能是如果用户有未读的消息,那么首次登录就弹框,如果用户关闭了页面,那么再次刷新页面的时候,也不再弹框,意思就是一个账户没

    2024年02月11日
    浏览(41)
  • 消息推送(websocket)集群化解决方案

    及时信息传递:消息推送功能能够确保网站向用户发送及时的重要信息,包括新闻更新、促销活动、账户状态变更等。这样可以增强用户体验,同时也提高用户对网站的参与度。 个性化定制:消息推送功能可以根据用户的偏好和兴趣来定制推送内容,使用户能够接收到与其相

    2024年02月16日
    浏览(40)
  • SpringBoot + WebSocket+STOMP指定推送消息

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 本文将简单的描述SpringBoot + WebSocket+STOMP指定推送消息场景,不包含信息安全加密等,请勿用在生产环境。 JDK:11+ Maven: 3.5+ SpringBoot: 2.6+ stompjs@7.0.0 STOMP 是面向简

    2024年02月14日
    浏览(47)
  • SpringBoot+Netty+Websocket实现消息推送

    这样一个需求:把设备异常的状态每10秒推送到页面并且以弹窗弹出来,这个时候用Websocket最为合适,今天主要是后端代码展示。 添加依赖 定义netty端口号 netty服务器 Netty配置 管理全局Channel以及用户对应的channel(推送消息) 管道配置 自定义CustomChannelHandler 推送消息接口及

    2024年02月04日
    浏览(47)
  • thinkphp结合WebSocket 实时推送消息详细实例

    实时推送消息是现代 Web 应用程序中常见的一种需求,而 WebSocket 已成为实时通信的首选技术。ThinkPHP 提供了对 WebSocket 的支持,本文将演示如何使用 ThinkPHP 实现 WebSocket 实时推送消息的详细例子。 安装 Swoole 在开始之前,你需要先安装 Swoole 扩展。可以使用以下命令来安装:

    2024年03月20日
    浏览(51)
  • SpringBoot整合Netty+Websocket实现消息推送

           Netty是一个高性能、异步事件驱动的网络应用框架,用于快速开发可维护的高性能协议服务器和客户端。以下是Netty的主要优势: 高性能 :Netty基于NIO(非阻塞IO)模型,采用事件驱动的设计,具有高性能的特点。它通过零拷贝技术、内存池化技术等手段,进一步提高

    2024年01月20日
    浏览(43)
  • Spring Boot集成WebSocket实现消息推送

    项目中经常会用到消息推送功能,关于推送技术的实现,我们通常会联想到轮询、comet长连接技术,虽然这些技术能够实现,但是需要反复连接,对于服务资源消耗过大,随着技术的发展,HtML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

    2023年04月08日
    浏览(44)
  • gateway管理websocket长连接服务,消息推送

    目前业务需要长连接进行实时数据交互,同时已有的业务系统统一经过gateway网关调度,websocket服务是有状态服务,所以希望集成到同一个注册中心让gateway进行长连接的负载均衡转发和管理,以此写个demo进行测试 提供http请求api和长连接进行消息发送  首先连接需要登录后获

    2023年04月13日
    浏览(39)
  • Springboot整合WebSocket实现主动向前端推送消息

            在上篇文章tcp编程中,我们实现了C++客户端与java服务器之间的通信,客户端发送了一个消息给服务器,今天我们要实现基于WebSocket实现服务器主动向前端推送消息,并且以服务器接收到C++客户端的消息主动向前端推送消息的触发条件。 WebSocket 的诞生背景       

    2024年03月16日
    浏览(41)
  • 分布式WebSocket消息推送系统设计与实现

    作者:禅与计算机程序设计艺术 现如今,随着物联网、云计算、移动互联网、大数据等新技术的兴起,分布式系统成为越来越多企业面临的挑战。在分布式系统中,服务间通信是一个重要且复杂的课题,基于TCP/IP协议族的传输层协议之上的应用层协议比如HTTP协议、RPC(Remo

    2024年02月05日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包