原文地址:https://www.cnblogs.com/betterboyz/p/8669879.html

WebSocket协议

WebSocket是一种在单个TCP连接上进行全双工通讯的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输

STOMP协议

STOMP是面向文本的消息传送协议。STOMP客户端与支持STOMP协议的消息代理进行通信。STOMP使用不同的命令,如连接,发送,订阅,断开等进行通信。

SockJS

SockJS是一个JavaScript库,提供跨浏览器JavaScript的API,创建了一个低延迟、全双工的浏览器和web服务器之间通信通道


以上内容出自维基百科和百度百科

使用websocket有两种方式:1是使用sockjs,2是使用h5的标准。使用Html5标准自然更方便简单,所以记录的是配合h5的使用方法。

1、pom引入

 核心是@ServerEndpoint这个注解。这个注解是Javaee标准里的注解,tomcat7以上已经对其进行了实现,如果是用传统方法使用tomcat发布项目,只要在pom文件中引入javaee标准即可使用。

  1. 1 <dependency>
  2. 2 <groupId>javax</groupId>
  3. 3 <artifactId>javaee-api</artifactId>
  4. 4 <version>7.0</version>
  5. 5 <scope>provided</scope>
  6. 6 </dependency>

但使用springboot的内置tomcat时,就不需要引入javaee-api了,spring-boot已经包含了。使用springboot的websocket功能首先引入springboot组件。

  1. 1 <dependency>
  2. 2 <groupId>org.springframework.boot</groupId>
  3. 3 <artifactId>spring-boot-starter-websocket</artifactId>
  4. 4 </dependency>

2、使用@ServerEndpoint创立websocket endpoint

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

  1. 1 @Configuration
  2. 2 public class WebSocketConfig {
  3. 3 @Bean
  4. 4 public ServerEndpointExporter serverEndpointExporter() {
  5. 5 return new ServerEndpointExporter();
  6. 6 }
  7. 7
  8. 8 }

下面事websocket的具体实现方法,代码如下:

  1. 1 import org.springframework.stereotype.Component;
  2. 2
  3. 3 import javax.websocket.*;
  4. 4 import javax.websocket.server.ServerEndpoint;
  5. 5 import java.io.IOException;
  6. 6 import java.util.concurrent.CopyOnWriteArraySet;
  7. 7
  8. 8 @ServerEndpoint(value = "/websocket")
  9. 9 @Component //此注解千万千万不要忘记,它的主要作用就是将这个监听器纳入到Spring容器中进行管理
  10. 10 public class WebSocket {
  11. 11 //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
  12. 12 private static int onlineCount = 0;
  13. 13
  14. 14 //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
  15. 15 private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<WebSocket>();
  16. 16
  17. 17 //与某个客户端的连接会话,需要通过它来给客户端发送数据
  18. 18 private Session session;
  19. 19
  20. 20 /**
  21. 21 * 连接建立成功调用的方法
  22. 22 */
  23. 23 @OnOpen
  24. 24 public void onOpen(Session session) {
  25. 25 this.session = session;
  26. 26 webSocketSet.add(this); //加入set中
  27. 27 addOnlineCount(); //在线数加1
  28. 28 System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
  29. 29 try {
  30. 30 sendMessage("Hello world");
  31. 31 } catch (IOException e) {
  32. 32 System.out.println("IO异常");
  33. 33 }
  34. 34 }
  35. 35
  36. 36 /**
  37. 37 * 连接关闭调用的方法
  38. 38 */
  39. 39 @OnClose
  40. 40 public void onClose() {
  41. 41 webSocketSet.remove(this); //从set中删除
  42. 42 subOnlineCount(); //在线数减1
  43. 43 System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
  44. 44 }
  45. 45
  46. 46 /**
  47. 47 * 收到客户端消息后调用的方法
  48. 48 *
  49. 49 * @param message 客户端发送过来的消息
  50. 50 */
  51. 51 @OnMessage
  52. 52 public void onMessage(String message, Session session) {
  53. 53 System.out.println("来自客户端的消息:" + message);
  54. 54
  55. 55 //群发消息
  56. 56 for (WebSocket item : webSocketSet) {
  57. 57 try {
  58. 58 item.sendMessage(message);
  59. 59 } catch (IOException e) {
  60. 60 e.printStackTrace();
  61. 61 }
  62. 62 }
  63. 63 }
  64. 64
  65. 65 /**
  66. 66 * 发生错误时调用
  67. 67 */
  68. 68 @OnError
  69. 69 public void onError(Session session, Throwable error) {
  70. 70 System.out.println("发生错误");
  71. 71 error.printStackTrace();
  72. 72 }
  73. 73
  74. 74
  75. 75 public void sendMessage(String message) throws IOException {
  76. 76 this.session.getBasicRemote().sendText(message);
  77. 77 //this.session.getAsyncRemote().sendText(message);
  78. 78 }
  79. 79
  80. 80
  81. 81 /**
  82. 82 * 群发自定义消息
  83. 83 */
  84. 84 public static void sendInfo(String message) throws IOException {
  85. 85 for (WebSocket item : webSocketSet) {
  86. 86 try {
  87. 87 item.sendMessage(message);
  88. 88 } catch (IOException e) {
  89. 89 continue;
  90. 90 }
  91. 91 }
  92. 92 }
  93. 93
  94. 94 public static synchronized int getOnlineCount() {
  95. 95 return onlineCount;
  96. 96 }
  97. 97
  98. 98 public static synchronized void addOnlineCount() {
  99. 99 WebSocket.onlineCount++;
  100. 100 }
  101. 101
  102. 102 public static synchronized void subOnlineCount() {
  103. 103 WebSocket.onlineCount--;
  104. 104 }
  105. 105 }

使用springboot的唯一区别是要@Component声明下,而使用独立容器是由容器自己管理websocket的,但在springboot中连容器都是spring管理的。

虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来。

3、前端代码

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

以上代码,实现了websocket简单消息推送,可以实现两个页面间的消息显示,但是Java后台主动推送消息时,无法获取消息推送的websocket下的session,即无法实现websocket下session的共享。

为解决主动推送的难题,需要在建立连接时,将websocket下的session与servlet下的HttpSession(或者其他session,我们这用到了shiro下的session)建立关联关系。

webSocket配置Java类:

  1. 1 import com.bootdo.common.utils.ShiroUtils;
  2. 2 import org.apache.catalina.session.StandardSessionFacade;
  3. 3 import org.apache.shiro.session.Session;
  4. 4 import org.springframework.context.annotation.Bean;
  5. 5 import org.springframework.context.annotation.Configuration;
  6. 6 import org.springframework.web.socket.server.standard.ServerEndpointExporter;
  7. 7
  8. 8 import javax.servlet.http.HttpSession;
  9. 9 import javax.websocket.HandshakeResponse;
  10. 10 import javax.websocket.server.HandshakeRequest;
  11. 11 import javax.websocket.server.ServerEndpointConfig;
  12. 12 import javax.websocket.server.ServerEndpointConfig.Configurator;
  13. 13
  14. 14 @Configuration
  15. 15 public class WebSocketConfig extends Configurator {
  16. 16
  17. 17 @Override
  18. 18 public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
  19. 19 /*如果没有监听器,那么这里获取到的HttpSession是null*/
  20. 20 StandardSessionFacade ssf = (StandardSessionFacade) request.getHttpSession();
  21. 21 if (ssf != null) {
  22. 22 HttpSession httpSession = (HttpSession) request.getHttpSession();
  23. 23 //关键操作
  24. 24 sec.getUserProperties().put("sessionId", httpSession.getId());
  25. 25 System.out.println("获取到的SessionID:" + httpSession.getId());
  26. 26 }
  27. 27 }
  28. 28
  29. 29 /**
  30. 30 * 引入shiro框架下的session,获取session信息
  31. 31 */
  32. 32 /*
  33. 33 @Override
  34. 34 public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
  35. 35 Session shiroSession = ShiroUtils.getSubjct().getSession();
  36. 36 sec.getUserProperties().put("sessionId", shiroSession.getId());
  37. 37 }
  38. 38 */
  39. 39
  40. 40 @Bean
  41. 41 public ServerEndpointExporter serverEndpointExporter() {
  42. 42 //这个对象说一下,貌似只有服务器是tomcat的时候才需要配置,具体我没有研究
  43. 43 return new ServerEndpointExporter();
  44. 44 }
  45. 45 }

webSocket消息实现类方法:

  1. 1 import org.springframework.stereotype.Component;
  2. 2
  3. 3 import javax.websocket.*;
  4. 4 import javax.websocket.server.ServerEndpoint;
  5. 5 import java.io.IOException;
  6. 6 import java.util.concurrent.CopyOnWriteArraySet;
  7. 7
  8. 8 //configurator = WebsocketConfig.class 该属性就是我上面配置的信息
  9. 9 @ServerEndpoint(value = "/websocket", configurator = WebSocketConfig.class)
  10. 10 @Component //此注解千万千万不要忘记,它的主要作用就是将这个监听器纳入到Spring容器中进行管理
  11. 11 public class WebSocket {
  12. 12 //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
  13. 13 private static int onlineCount = 0;
  14. 14
  15. 15 //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
  16. 16 private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<WebSocket>();
  17. 17
  18. 18 //与某个客户端的连接会话,需要通过它来给客户端发送数据
  19. 19 private Session session;
  20. 20
  21. 21 /**
  22. 22 * 连接建立成功调用的方法
  23. 23 * <p>
  24. 24 * config用来获取WebsocketConfig中的配置信息
  25. 25 */
  26. 26 @OnOpen
  27. 27 public void onOpen(Session session, EndpointConfig config) {
  28. 28
  29. 29 //获取WebsocketConfig.java中配置的“sessionId”信息值
  30. 30 String httpSessionId = (String) config.getUserProperties().get("sessionId");
  31. 31
  32. 32 this.session = session;
  33. 33 webSocketSet.add(this); //加入set中
  34. 34 addOnlineCount(); //在线数加1
  35. 35 System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
  36. 36 try {
  37. 37 sendMessage("Hello world");
  38. 38 } catch (IOException e) {
  39. 39 System.out.println("IO异常");
  40. 40 }
  41. 41 }
  42. 42
  43. 43 /**
  44. 44 * 连接关闭调用的方法
  45. 45 */
  46. 46 @OnClose
  47. 47 public void onClose() {
  48. 48 webSocketSet.remove(this); //从set中删除
  49. 49 subOnlineCount(); //在线数减1
  50. 50 System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
  51. 51 }
  52. 52
  53. 53 /**
  54. 54 * 收到客户端消息后调用的方法
  55. 55 *
  56. 56 * @param message 客户端发送过来的消息
  57. 57 */
  58. 58 @OnMessage
  59. 59 public void onMessage(String message, Session session) {
  60. 60 System.out.println("来自客户端的消息:" + message);
  61. 61
  62. 62 //群发消息
  63. 63 for (WebSocket item : webSocketSet) {
  64. 64 try {
  65. 65 item.sendMessage(message);
  66. 66 } catch (IOException e) {
  67. 67 e.printStackTrace();
  68. 68 }
  69. 69 }
  70. 70 }
  71. 71
  72. 72 /**
  73. 73 * 发生错误时调用
  74. 74 */
  75. 75 @OnError
  76. 76 public void onError(Session session, Throwable error) {
  77. 77 System.out.println("发生错误");
  78. 78 error.printStackTrace();
  79. 79 }
  80. 80
  81. 81
  82. 82 public void sendMessage(String message) throws IOException {
  83. 83 this.session.getBasicRemote().sendText(message);
  84. 84 //this.session.getAsyncRemote().sendText(message);
  85. 85 }
  86. 86
  87. 87
  88. 88 /**
  89. 89 * 群发自定义消息
  90. 90 */
  91. 91 public static void sendInfo(String message) throws IOException {
  92. 92 for (WebSocket item : webSocketSet) {
  93. 93 try {
  94. 94 item.sendMessage(message);
  95. 95 } catch (IOException e) {
  96. 96 continue;
  97. 97 }
  98. 98 }
  99. 99 }
  100. 100
  101. 101 public static synchronized int getOnlineCount() {
  102. 102 return onlineCount;
  103. 103 }
  104. 104
  105. 105 public static synchronized void addOnlineCount() {
  106. 106 WebSocket.onlineCount++;
  107. 107 }
  108. 108
  109. 109 public static synchronized void subOnlineCount() {
  110. 110 WebSocket.onlineCount--;
  111. 111 }
  112. 112 }

注意,有上面配置后,如果配置获取的信息为null,需加入监听实现类:

  1. 1 import org.springframework.stereotype.Component;
  2. 2
  3. 3 import javax.servlet.ServletRequestEvent;
  4. 4 import javax.servlet.ServletRequestListener;
  5. 5 import javax.servlet.http.HttpServletRequest;
  6. 6 import javax.servlet.http.HttpSession;
  7. 7
  8. 8 /**
  9. 9 * 监听器类:主要任务是用ServletRequest将我们的HttpSession携带过去
  10. 10 */
  11. 11 @Component //此注解千万千万不要忘记,它的主要作用就是将这个监听器纳入到Spring容器中进行管理,相当于注册监听吧
  12. 12 public class RequestListener implements ServletRequestListener {
  13. 13 @Override
  14. 14 public void requestInitialized(ServletRequestEvent sre) {
  15. 15 //将所有request请求都携带上httpSession
  16. 16 HttpSession httpSession= ((HttpServletRequest) sre.getServletRequest()).getSession();
  17. 17 System.out.println("将所有request请求都携带上httpSession " + httpSession.getId());
  18. 18 }
  19. 19
  20. 20 public RequestListener() {
  21. 21 }
  22. 22
  23. 23 @Override
  24. 24 public void requestDestroyed(ServletRequestEvent arg0) {
  25. 25 }
  26. 26 }

对应的前端页面无需改变。

spring boot下WebSocket消息推送(转)的更多相关文章

  1. spring boot下WebSocket消息推送

    WebSocket协议 WebSocket是一种在单个TCP连接上进行全双工通讯的协议.WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范.WebSo ...

  2. 【WebSocket】WebSocket消息推送

    准备使用WebSocket实现Java与Vue或者安卓间的实时通信,实现私密聊天.群聊.查询下资料备用. WebSocket客户端 websocket允许通过JavaScript建立与远程服务器的连接 ...

  3. node.js Websocket消息推送---GoEasy

    Goeasy, 它是一款第三方推送服务平台,使用它的API可以轻松搞定实时推送!个人感觉goeasy推送更稳定,推送 速度快,代码简单易懂上手快 浏览器兼容性:GoEasy推送 支持websocket ...

  4. C(++) Websocket消息推送---GoEasy

    Goeasy, 它是一款第三方推送服务平台,使用它的API可以轻松搞定实时推送!个人感觉goeasy推送更稳定,推送 速度快,代码简单易懂上手快 浏览器兼容性:GoEasy推送 支持websocket ...

  5. spring+rabbitmq+stomp搭建websocket消息推送(非spring boot方式)

    前言: 两年前做过spring+activemq+stomp的ws推送,那个做起来很简单,但现在公司用的mq中间件是rabbitmq,因此需要通过rabbitmq去做ws通信.仔细搜了搜百度/谷歌,网 ...

  6. websocket消息推送实现

    一.服务层 package com.demo.websocket; import java.io.IOException; import java.util.Iterator; import java ...

  7. WebSocket消息推送

    WebSocket协议是基于TCP的一种新的网络协议,应用层,是TCP/IP协议的子集. 它实现了浏览器与服务器全双工(full-duplex)通信,客户端和服务器都可以向对方主动发送和接收数据.在J ...

  8. Spring mvc服务端消息推送(SSE技术)

    SSE技术是基于单工通信模式,只是单纯的客户端向服务端发送请求,服务端不会主动发送给客户端.服务端采取的策略是抓住这个请求不放,等数据更新的时候才返回给客户端,当客户端接收到消息后,再向服务端发送请求 ...

  9. [置顶] spring集成mina 实现消息推送以及转发

    spring集成mina: 在学习mina这块时,在网上找了很多资料,只有一些demo,只能实现客户端向服务端发送消息.建立长连接之类.但是实际上在项目中,并不简单实现这些,还有业务逻辑之类的处理以及 ...

随机推荐

  1. php中NULL、false、0、" "有何区别?

    php中很多还不懂php中0,"",null和false之间的区别,这些区别有时会影响到数据判断的正确性和安全性,给程序的测试运行造成很多麻烦.先看一个例子: <? $str ...

  2. .net中的泛型

    泛型把类或方法的类型的确定推迟到实例化该类或方法的时候 ,也就是说刚开始声明是不指定类型,等到要使用(实例化)时再指定类型 泛型可以用于  类.方法.委托.事件等 下面先写一个简单的泛型 public ...

  3. (判断url文件大小)关于inputStream.available()方法获取下载文件的总大小

    转自:http://hold-on.iteye.com/blog/1017449 如果用inputStream对象的available()方法获取流中可读取的数据大小,通常我们调用这个函数是在下载文件 ...

  4. asp.net <%%> <%#%><%=%><%@%><%$%>用法区别

    asp.net <%%>&<%#%>&<%=%>&<%@%>&<%$%>用法区别 1.<% %> ...

  5. Java数据库编程——事务

    我们可以将一组语句构建成一个事务(transaction).当所有语句都顺利执行之后,事务可以提交(commit).否则,如果其中某个语句遇到错误,那么事务将被回滚,就好像没有任何语句被执行过一样. ...

  6. MR 文件合并

    package com.euphe.filter; import com.euphe.util.HUtils; import com.euphe.util.Utils; import org.apac ...

  7. Linux远程执行Windows机器任务

    Linux远程执行Windows机器任务     近期测试人员提出需求需要在Linux下调用Windows系统下的cmd的命令完成自动构建和测试并生成测试报告. 环境: Windows Server2 ...

  8. 【原创】Android自定义适配器的使用方法

    比如说我们已经得到了数据,想在一个listview或者在其他的控件中显示的,并且我们显示想要自己设计样式来显示的话就要用到自定义适配器了,下面让我们结合代码讲一下具体的使用方法: 代码会有注释的哦: ...

  9. MyEclipse 集成 Gradle开发环境

    一.上Grandle官网下载Gradle,地址:http://www.gradle.org/downloads 如果只是运行只下载gradle-2.6-bin.zip 就可以了,如果为了扩展开发的话就 ...

  10. Solidworks如何让齿轮运动副保证持续啮合状态

    出现这种情况一般是齿轮的比例有问题,如果你选择两个齿轮的齿顶圆的面,则自动比例是44:74,然后手动转动某个齿轮,就会出现不能啮合的情况   只要模数相同的齿轮不管大小都能始终啮合,但是你需要首先为每 ...