spring boot 集成 websocket 实现消息主动

前言

http协议是无状态协议,每次请求都不知道前面发生了什么,而且只可以由浏览器端请求服务器端,而不能由服务器去主动通知浏览器端,是单向的,在很多场景就不适合,比如实时的推送,消息通知或者股票等信息的推送;在没有 websocket 之前,要解决这种问题,只能依靠 ajax轮询 或者 长轮询,这两种方式极大的消耗资源;而websocket,只需要借助http协议进行握手,然后保持着一个websocket连接,直到客户端主动断开;相对另外的两种方式,websocket只进行一次连接,当有数据的时候再推送给浏览器,减少带宽的浪费和cpu的使用。
       WebSocket是html5新增加的一种通信协议,目前流行的浏览器都支持这个协议,例如Chrome,Safari,Firefox,Opera,IE等等,对该协议支持最早的应该是chrome,从chrome12就已经开始支持,随着协议草案的不断变化,各个浏览器对协议的实现也在不停的更新。该协议还是草案,没有成为标准,不过成为标准应该只是时间问题了,从WebSocket草案的提出到现在已经有十几个版本了,目前对该协议支持最完善的浏览器应该是chrome,毕竟WebSocket协议草案也是Google发布的,下面我们教程我们使用springboot 集成 websocket 实现消息的一对一以及全部通知功能。

本文使用spring boot 2.1.1+ jdk1.8 + idea。

一:引入依赖

如何创建springboot项目本文不再赘述,首先在创建好的项目pom.xml中引入如下依赖:

<!--引入websocket依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

<!--引入thymeleaf模板引擎依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

二:创建websocket配置类

ServerEndpointExporter 会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint。要注意,如果使用独立的servlet容器,而不是直接使用springboot的内置容器,就不要注入ServerEndpointExporter,因为它将由容器自己提供和管理。

  1. package com.sailing.websocket.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.socket.server.standard.ServerEndpointExporter;
  5. /**
  6. * @author baibing
  7. * @project: springboot-socket
  8. * @package: com.sailing.websocket.config
  9. * @Description: socket配置类,往 spring 容器中注入ServerEndpointExporter实例
  10. * @date 2018/12/20 09:46
  11. */
  12. @Configuration
  13. public class WebSocketConfig {
  14. @Bean
  15. public ServerEndpointExporter serverEndpointExporter(){
  16. return new ServerEndpointExporter();
  17. }
  18. }

三:编写websocket服务端代码

  1. package com.sailing.websocket.common;
  2. import org.springframework.stereotype.Component;
  3. import javax.websocket.*;
  4. import javax.websocket.server.PathParam;
  5. import javax.websocket.server.ServerEndpoint;
  6. import java.io.IOException;
  7. import java.util.HashMap;
  8. import java.util.Map;
  9. import java.util.concurrent.atomic.AtomicInteger;
  10. /**
  11. * @author baibing
  12. * @project: springboot-socket
  13. * @package: com.sailing.websocket.common
  14. * @Description: WebSocket服务端代码,包含接收消息,推送消息等接口
  15. * @date 2018/12/200948
  16. */
  17. @Component
  18. @ServerEndpoint(value = "/socket/{name}")
  19. public class WebSocketServer {
  20. //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
  21. private static AtomicInteger online = new AtomicInteger();
  22. //concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。
  23. private static Map<String,Session> sessionPools = new HashMap<>();
  24. /**
  25. * 发送消息方法
  26. * @param session 客户端与socket建立的会话
  27. * @param message 消息
  28. * @throws IOException
  29. */
  30. public void sendMessage(Session session, String message) throws IOException{
  31. if(session != null){
  32. session.getBasicRemote().sendText(message);
  33. }
  34. }
  35. /**
  36. * 连接建立成功调用
  37. * @param session 客户端与socket建立的会话
  38. * @param userName 客户端的userName
  39. */
  40. @OnOpen
  41. public void onOpen(Session session, @PathParam(value = "name") String userName){
  42. sessionPools.put(userName, session);
  43. addOnlineCount();
  44. System.out.println(userName + "加入webSocket!当前人数为" + online);
  45. try {
  46. sendMessage(session, "欢迎" + userName + "加入连接!");
  47. } catch (IOException e) {
  48. e.printStackTrace();
  49. }
  50. }
  51. /**
  52. * 关闭连接时调用
  53. * @param userName 关闭连接的客户端的姓名
  54. */
  55. @OnClose
  56. public void onClose(@PathParam(value = "name") String userName){
  57. sessionPools.remove(userName);
  58. subOnlineCount();
  59. System.out.println(userName + "断开webSocket连接!当前人数为" + online);
  60. }
  61. /**
  62. * 收到客户端消息时触发(群发)
  63. * @param message
  64. * @throws IOException
  65. */
  66. @OnMessage
  67. public void onMessage(String message) throws IOException{
  68. for (Session session: sessionPools.values()) {
  69. try {
  70. sendMessage(session, message);
  71. } catch(Exception e){
  72. e.printStackTrace();
  73. continue;
  74. }
  75. }
  76. }
  77. /**
  78. * 发生错误时候
  79. * @param session
  80. * @param throwable
  81. */
  82. @OnError
  83. public void onError(Session session, Throwable throwable){
  84. System.out.println("发生错误");
  85. throwable.printStackTrace();
  86. }
  87. /**
  88. * 给指定用户发送消息
  89. * @param userName 用户名
  90. * @param message 消息
  91. * @throws IOException
  92. */
  93. public void sendInfo(String userName, String message){
  94. Session session = sessionPools.get(userName);
  95. try {
  96. sendMessage(session, message);
  97. }catch (Exception e){
  98. e.printStackTrace();
  99. }
  100. }
  101. public static void addOnlineCount(){
  102. online.incrementAndGet();
  103. }
  104. public static void subOnlineCount() {
  105. online.decrementAndGet();
  106. }
  107. }

四:增加测试页面路由配置类

如果为每一个页面写一个action太麻烦,spring boot 提供了页面路由的统一配置,在 spring boot 2.0 以前的版本中我们只需要继承 WebMvcConfigurerAdapter ,并重写它的 addViewControllers 方法即可,但是 2.0版本后 WebMvcConfigurerAdapter已经被废弃,使用 WebMvcConfigurer 接口代替(其实WebMvcConfigurerAdapter也是实现了WebMvcConfigurer),所以我们只需要实现它即可:

  1. package com.sailing.websocket.config;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
  4. import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
  5. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  6. import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
  7. /**
  8. * 在SpringBoot2.0及Spring 5.0 WebMvcConfigurerAdapter已被废弃,目前找到解决方案就有
  9. * 1 直接实现WebMvcConfigurer (官方推荐)
  10. * 2 直接继承WebMvcConfigurationSupport
  11. * @ https://blog.csdn.net/lenkvin/article/details/79482205
  12. */
  13. @Configuration
  14. public class WebMvcConfig implements WebMvcConfigurer {
  15. /**
  16. * 为各个页面提供路径映射
  17. * @param registry
  18. */
  19. @Override
  20. public void addViewControllers(ViewControllerRegistry registry) {
  21. registry.addViewController("/client").setViewName("client");
  22. registry.addViewController("/index").setViewName("index");
  23. }
  24. }

五:创建测试页面

在 resources下面创建 templates 文件夹,编写两个测试页面 index.html 和 client.html 和上面配置类中的viewName相对应,两个页面内容一模一样,只是在连接websocket部分模拟的用户名不一样,一个叫 lucy 一个叫 lily :

  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <title>WebSocket</title>
  5. </head>
  6. <body>
  7. Welcome<br/>
  8. <input id="text" type="text" /><button onclick="send()">Send</button> <button onclick="closeWebSocket()">Close</button>
  9. <div id="message">
  10. </div>
  11. </body>
  12. <script type="text/javascript">
  13. var websocket = null;
  14. //判断当前浏览器是否支持WebSocket
  15. if('WebSocket' in window){
  16. websocket = new WebSocket("ws://localhost:8080/socket/lucy");
  17. }else{
  18. alert('Not support websocket')
  19. }
  20. //连接发生错误的回调方法
  21. websocket.onerror = function(){
  22. setMessageInnerHTML("error");
  23. };
  24. //连接成功建立的回调方法
  25. websocket.onopen = function(event){
  26. setMessageInnerHTML("open");
  27. }
  28. //接收到消息的回调方法
  29. websocket.onmessage = function(event){
  30. setMessageInnerHTML(event.data);
  31. }
  32. //连接关闭的回调方法
  33. websocket.onclose = function(){
  34. setMessageInnerHTML("close");
  35. }
  36. //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
  37. window.onbeforeunload = function(){
  38. websocket.close();
  39. }
  40. //将消息显示在网页上
  41. function setMessageInnerHTML(innerHTML){
  42. document.getElementById('message').innerHTML += innerHTML + '<br/>';
  43. }
  44. //关闭连接
  45. function closeWebSocket(){
  46. websocket.close();
  47. }
  48. //发送消息
  49. function send(){
  50. var message = document.getElementById('text').value;
  51. websocket.send(message);
  52. }
  53. </script>
  54. </html>
  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <title>WebSocket</title>
  5. </head>
  6. <body>
  7. Welcome<br/>
  8. <input id="text" type="text" /><button onclick="send()">Send</button> <button onclick="closeWebSocket()">Close</button>
  9. <div id="message">
  10. </div>
  11. </body>
  12. <script type="text/javascript">
  13. var websocket = null;
  14. //判断当前浏览器是否支持WebSocket
  15. if('WebSocket' in window){
  16. websocket = new WebSocket("ws://localhost:8080/socket/lily");
  17. }else{
  18. alert('Not support websocket')
  19. }
  20. //连接发生错误的回调方法
  21. websocket.onerror = function(){
  22. setMessageInnerHTML("error");
  23. };
  24. //连接成功建立的回调方法
  25. websocket.onopen = function(event){
  26. setMessageInnerHTML("open");
  27. }
  28. //接收到消息的回调方法
  29. websocket.onmessage = function(event){
  30. setMessageInnerHTML(event.data);
  31. }
  32. //连接关闭的回调方法
  33. websocket.onclose = function(){
  34. setMessageInnerHTML("close");
  35. }
  36. //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
  37. window.onbeforeunload = function(){
  38. websocket.close();
  39. }
  40. //将消息显示在网页上
  41. function setMessageInnerHTML(innerHTML){
  42. document.getElementById('message').innerHTML += innerHTML + '<br/>';
  43. }
  44. //关闭连接
  45. function closeWebSocket(){
  46. websocket.close();
  47. }
  48. //发送消息
  49. function send(){
  50. var message = document.getElementById('text').value;
  51. websocket.send(message);
  52. }
  53. </script>
  54. </html>

六:测试webscoket controller

  1. package com.sailing.websocket.controller;
  2. import com.sailing.websocket.common.WebSocketServer;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. import org.springframework.web.bind.annotation.RequestMethod;
  5. import org.springframework.web.bind.annotation.RequestParam;
  6. import org.springframework.web.bind.annotation.RestController;
  7. import javax.annotation.Resource;
  8. import java.io.IOException;
  9. /**
  10. * @author baibing
  11. * @project: springboot-socket
  12. * @package: com.sailing.websocket.controller
  13. * @Description: websocket测试controller
  14. * @date 2018/12/20 10:11
  15. */
  16. @RestController
  17. public class SocketController {
  18. @Resource
  19. private WebSocketServer webSocketServer;
  20. /**
  21. * 给指定用户推送消息
  22. * @param userName 用户名
  23. * @param message 消息
  24. * @throws IOException
  25. */
  26. @RequestMapping(value = "/socket", method = RequestMethod.GET)
  27. public void testSocket1(@RequestParam String userName, @RequestParam String message){
  28. webSocketServer.sendInfo(userName, message);
  29. }
  30. /**
  31. * 给所有用户推送消息
  32. * @param message 消息
  33. * @throws IOException
  34. */
  35. @RequestMapping(value = "/socket/all", method = RequestMethod.GET)
  36. public void testSocket2(@RequestParam String message){
  37. try {
  38. webSocketServer.onMessage(message);
  39. } catch (IOException e) {
  40. e.printStackTrace();
  41. }
  42. }
  43. }

七:测试

访问 http://localhost:8080/index 和 http://localhost:8080/client 分别打开两个页面并连接到websocket,http://localhost:8080/socket?userName=lily&message=helloworld 给lily发送消息,http://localhost:8080/socket/all?message=LOL 给全部在线用户发送消息:

spring boot 集成 websocket 实现消息主动推送的更多相关文章

  1. Spring Boot 集成 WebSocket 实现服务端推送消息到客户端

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

  2. spring boot 集成 websocket 实现消息主动

    来源:https://www.cnblogs.com/leigepython/p/11058902.html pom.xml 1 <?xml version="1.0" en ...

  3. PHP版微信公共平台消息主动推送,突破订阅号一天只能发送一条信息限制

    2013年10月06日最新整理. PHP版微信公共平台消息主动推送,突破订阅号一天只能发送一条信息限制 微信公共平台消息主动推送接口一直是腾讯的私用接口,相信很多朋友都非常想要用到这个功能. 通过学习 ...

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

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

  5. spring boot集成Websocket

    websocket实现后台像前端主动推送消息的模式,可以减去前端的请求获取数据的模式.而后台主动推送消息一般都是要求消息回馈比较及时,同时减少前端ajax轮询请求,减少资源开销. spring boo ...

  6. Spring boot集成Websocket,前端监听心跳实现

    第一:引入jar 由于项目是springboot的项目所以我这边简单的应用了springboot自带的socket jar <dependency> <groupId>org. ...

  7. 【转】Spring+Websocket实现消息的推送

    本文主要有三个步骤 1.用户登录后建立websocket连接,默认选择websocket连接,如果浏览器不支持,则使用sockjs进行模拟连接 2.建立连接后,服务端返回该用户的未读消息 3.服务端进 ...

  8. Channels集成到Django消息实时推送

    channel架构图 InterFace Server:负责对协议进行解析,将不同的协议分发到不同的Channel Channel Layer:频道层,可以是一个FIFO队列,通常使用Redis Dj ...

  9. spring boot集成websocket实现聊天功能和监控功能

    本文参考了这位兄台的文章: https://blog.csdn.net/ffj0721/article/details/82630134 项目源码url: https://github.com/zhz ...

随机推荐

  1. 回望2017,基于深度学习的NLP研究大盘点

    回望2017,基于深度学习的NLP研究大盘点 雷锋网 百家号01-0110:31 雷锋网 AI 科技评论按:本文是一篇发布于 tryolabs 的文章,作者 Javier Couto 针对 2017 ...

  2. 004-windows(64位)下使用curl命令

    一.下载工具包:http://curl.haxx.se/download.html 二.使用 使用方式一:在curl.exe目录中使用 解压下载后的压缩文件,通过cmd命令进入到curl.exe所在的 ...

  3. 阶段1 语言基础+高级_1-3-Java语言高级_1-常用API_1_第5节 String类_2_字符串的构造方法和直接创建

    string的构造方法 psvm创建main方法 把字节翻译成了小a小b小c.字符串的底层科室用的byte字节数组 Ctrl+鼠标左键点击string 这个byte就保存了字符串底层的字节数据 直接创 ...

  4. JavaScript 基础类型,数据类型

    1.基础类型:undefined,null,Boolean,Number,String,Symbol Undefined类型:一个没有被赋值的变量会有个默认值undefined; Null类型:nul ...

  5. 练习2:python-把excel表格中某张表的内容导入sqlite

    前言:最新需要用到大批量的数据,在excel造好数据之后,存储在数据库库中,方便调用数据,于是就想着用python语言写一下这个过程 python有个openpyxl的模块,可以直接用来对于excel ...

  6. python实现建立udp通信

    实现代码如下: #udp协议通信import socket,timeclass UdpConnect: def get_udp(self,ip,port,message): #建立udp连接 myso ...

  7. Java中StringHelp

    import java.util.Collection; import java.util.Map; import java.util.UUID; public class StringHelper ...

  8. debian下使用shell脚本时出现了 declare:not found 解决方法

    问题:出现declare:not found的提示 解决:原来,UBUNTU用的是dash(后来证明这个其实这个不是错误的原因:从#!/bin/bash到#!/bin/dash,依旧无法运行,在这写出 ...

  9. [Python3] 010 字符串:给你们看看我的内置方法 第二弹

    目录 少废话,上例子 1. isidentifier() 2. islower() 3. isnumeric() 4. isprintable() 5. isspace() 6. istitle() ...

  10. HDFS基本概念

    概念 HDFS,它是一个文件系统,用于存储文件,通过目录树来定位文件:其次,它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色. 注意:HDFS的设计适合一次写入,多次读出的场景 ...