1. public interface WebSocketMessageBrokerConfigurer {
  2.  
  3. // 添加这个Endpoint,这样在网页中就可以通过websocket连接上服务,也就是我们配置websocket的服务地址,并且可以指定是否使用socketjs
  4. void registerStompEndpoints(StompEndpointRegistry var1);
  5.  
  6. // 配置发送与接收的消息参数,可以指定消息字节大小,缓存大小,发送超时时间
  7. void configureWebSocketTransport(WebSocketTransportRegistration var1);
  8.  
  9. // 设置输入消息通道的线程数,默认线程为1,可以自己自定义线程数,最大线程数,线程存活时间
  10. void configureClientInboundChannel(ChannelRegistration var1);
  11.  
  12. // 设置输出消息通道的线程数,默认线程为1,可以自己自定义线程数,最大线程数,线程存活时间
  13. void configureClientOutboundChannel(ChannelRegistration var1);
  14.  
  15. // 添加自定义的消息转换器,spring 提供多种默认的消息转换器,返回false,不会添加消息转换器,返回true,会添加默认的消息转换器,当然也可以把自己写的消息转换器添加到转换链中
  16. boolean configureMessageConverters(List<MessageConverter> var1);
  17.  
  18. // 配置消息代理,哪种路径的消息会进行代理处理
  19. void configureMessageBroker(MessageBrokerRegistry var1);
  20.  
  21. // 自定义控制器方法的参数类型,有兴趣可以百度google HandlerMethodArgumentResolver这个的用法
  22. void addArgumentResolvers(List<HandlerMethodArgumentResolver> var1);
  23.  
  24. // 自定义控制器方法返回值类型,有兴趣可以百度google HandlerMethodReturnValueHandler这个的用法
  25. void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> var1);
  26. }

  1. package com.hainei.config;
  2.  
  3. import com.hainei.interceptor.HttpHandShakeInterceptor;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.messaging.simp.config.MessageBrokerRegistry;
  6. import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
  7. import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
  8. import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
  9.  
  10. /**
  11. * Created with IntelliJ IDEA.
  12. * User: lzx
  13. * Date: 2020/02/28
  14. * Time: 14:52
  15. * Description: No Description
  16. */
  17. @Configuration
  18. @EnableWebSocketMessageBroker
  19. public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
  20.  
  21. /**
  22. * 注册端点,发布或者订阅消息的时候需要连接此端点
  23. * setAllowedOrigins 非必须,*表示允许其他域进行连接
  24. * withSockJS 表示开始sockejs支持
  25. */
  26. @Override
  27. public void registerStompEndpoints(StompEndpointRegistry registry) {
  28. registry.addEndpoint("/samp-websocket")
  29. .addInterceptors(new HttpHandShakeInterceptor())
  30. .setAllowedOrigins("*")
  31. .withSockJS();
  32. }
  33. /**
  34. * 配置消息代理(中介)
  35. * enableSimpleBroker 服务端推送给客户端的路径前缀
  36. * setApplicationDestinationPrefixes 客户端发送数据给服务器端的一个前缀
  37. */
  38. @Override
  39. public void configureMessageBroker(MessageBrokerRegistry registry) {
  40.  
  41. //表示在topic和user这两个域上可以向客户端发消息
  42. registry.enableSimpleBroker("/topic", "/user");
  43. registry.setApplicationDestinationPrefixes("/app");
  44. //给指定用户发送一对一的主题前缀是"/user"
  45. registry.setUserDestinationPrefix("/user");
  46. }
  47. // @Override
  48. // public void configureClientInboundChannel(ChannelRegistration registration) {
  49. // registration.interceptors( new SocketChannelInterceptor());
  50. // }
  51. //
  52. // @Override
  53. // public void configureClientOutboundChannel(ChannelRegistration registration) {
  54. // registration.interceptors( new SocketChannelInterceptor());
  55. // }
  56. }

  1. package com.hainei.interceptor;
  2.  
  3. import com.hainei.common.constants.BaseConstant;
  4. import com.hainei.common.token.JwtTokenUtil;
  5. import org.apache.commons.lang3.StringUtils;
  6. import org.springframework.http.server.ServerHttpRequest;
  7. import org.springframework.http.server.ServerHttpResponse;
  8. import org.springframework.http.server.ServletServerHttpRequest;
  9. import org.springframework.web.socket.WebSocketHandler;
  10. import org.springframework.web.socket.server.HandshakeInterceptor;
  11.  
  12. import javax.servlet.http.HttpServletRequest;
  13. import javax.servlet.http.HttpSession;
  14. import java.net.URI;
  15. import java.util.Map;
  16.  
  17. /**
  18. * Created with IntelliJ IDEA.
  19. * User: lzx
  20. * Date: 2020/03/02
  21. * Time: 11:28
  22. * Description: No Description
  23. */
  24. public class HttpHandShakeInterceptor implements HandshakeInterceptor {
  25. @Override
  26. public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> attributes) throws Exception {
  27.  
  28. if(request instanceof ServletServerHttpRequest) {
  29. ServletServerHttpRequest servletRequest = (ServletServerHttpRequest)request;
  30. // String accessToken = servletRequest.getServletRequest().getHeader(BaseConstant.ACCESS_TOKEN);
  31. URI uri = servletRequest.getURI();
  32. String userId = StringUtils.substringAfter(uri.toString(), "userId=");
  33.  
  34. // String userId = JwtTokenUtil.getUserId(accessToken);
  35. System.out.println("【握手拦截器】beforeHandshake userId="+userId);
  36. attributes.put("userId", userId);
  37. }
  38. // if(request instanceof HttpServletRequest) {
  39. // HttpServletRequest httpServletRequest = (HttpServletRequest) request;
  40. // String accessToken=httpServletRequest.getHeader(BaseConstant.ACCESS_TOKEN);
  41. // if (StringUtils.isNotEmpty(accessToken)) {
  42. // System.out.println("token为空,设置为默认token");
  43. // accessToken = "eyJhbGciOiJIUzI1NiJ9.eyJqd3QtcGVybWlzc2lvbnMta2V5XyI6bnVsbC" +
  44. // "wic3ViIjoiZGUzOTFmNGU4ZTg3NDQwMGFlYjRlMzhiOGE3NDIyZjQiLCJqd3QtbG9naW4taWQta2V5" +
  45. // "Ijoi5p6X5a2Q57-UIiwiand0LWlzLWFkbWluIjowLCJpc3MiOiJ5aW5neHVlLmNvbSIsImV4cCI6MTU4" +
  46. // "MzEzNjk0MywiaWF0IjoxNTgzMTI5NzQzfQ.hshSbSkn3o7y9CGLb3LXvGBp4z8T7qbHXx2ItAhDMdc";
  47. // }
  48. // String userId = JwtTokenUtil.getUserId(accessToken);
  49. // System.out.println("【握手拦截器】beforeHandshake userId="+userId);
  50. // attributes.put("userId", userId);
  51. // }
  52. return true;
  53. }
  54.  
  55. @Override
  56. public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
  57.  
  58. }
  59. }
特别说明:1. 本文基于Springboot spring-boot-starter-parent 1.5.1.RELEASE编码,在不同的版本中部分方法有区别。2. 因为博客字数限制,拆分成了两篇文章
第一篇地址:https://www.jianshu.com/p/4762494d42f1
第二篇地址:https://www.jianshu.com/p/9103c9c7e128

作者:TryCatch菌
链接:https://www.jianshu.com/p/9103c9c7e128
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

前面两种建立websocket通讯,不管是用javax的包还是spring的包都是用的比较底层的协议,下面我们来看看用上层的STOMP来建立websocket通讯

SockJs+Spring-WebSocket时,由于SockJs与Spring WebSocket之间采用JSON通讯,需要引入jackson 2的相关jar包
  1. <!-- jackson-->
  2. <dependency>
  3. <groupId>com.fasterxml.jackson.core</groupId>
  4. <artifactId>jackson-core</artifactId>
  5. <version>2.6.3</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>com.fasterxml.jackson.core</groupId>
  9. <artifactId>jackson-databind</artifactId>
  10. <version>2.6.3</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>com.fasterxml.jackson.core</groupId>
  14. <artifactId>jackson-annotations</artifactId>
  15. <version>2.6.3</version>
  16. </dependency>

前面已经提到了STOMP是一个上层协议,STOMP 在 WebSocket 之上提供了一个基于 帧的线路格式层,用来定义消息语义。
STOMP 帧:该帧由命令,一个或多个 头信息 以及 负载所组成。如下就是发送 数据的一个 STOMP帧:

  1. SEND
  2. destination:/app/marco
  3. content-length:20
  4. {\"message\":\"hello word!\"}
  1. SEND:STOMP命令,表明会发送一些内容;
  2. destination:头信息,用来表示消息发送到哪里;
  3. content-length:头信息,用来表示 负载内容的 大小;
  4. 空行:
  5. 帧内容(负载)内容

要使用STOMP 通讯,服务端,和客户端都必须支持,服务端的准备步骤

服务端准备工作

  1. 我们已经配置了STOMP通讯的配置类 WebSocketStompConfig

  2. 配置了WebSocketChannelInterceptor 和 WebSocketHandshakeInterceptor 两个自定义拦截器

  3. 一个WebSocketStompController 用于接收客户端消息和响应客户端

  4. 一个简单的MVC controller 用于跳转websocket 页面

在Spring中启用STOMP通讯不用我们自己去写原生态的帧,spring的消息功能是基于代理模式构建,其实说得复杂,都是封装好了的,如果需要开启SOMP,只需要在websocket配置类上使用@EnableWebSocketMessageBroker (注解的作用为能够在 WebSocket 上启用 STOMP),并实现WebSocketMessageBrokerConfigurer接口,有些教程在这一步会继承AbstractWebSocketMessageBrokerConfigurer 类,我们看一下AbstractWebSocketMessageBrokerConfigurer类的源码,可以看到都是空方法,也是实现的接口,这里推荐自己实现接口,因为官方API上AbstractWebSocketMessageBrokerConfigurer已经标记为废弃

 
image.png

AbstractWebSocketMessageBrokerConfigurer 抽象类

  1. public abstract class AbstractWebSocketMessageBrokerConfigurer implements WebSocketMessageBrokerConfigurer {
  2. public AbstractWebSocketMessageBrokerConfigurer() {
  3. }
  4. public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
  5. }
  6. public void configureClientInboundChannel(ChannelRegistration registration) {
  7. }
  8. public void configureClientOutboundChannel(ChannelRegistration registration) {
  9. }
  10. public boolean configureMessageConverters(List<MessageConverter> messageConverters) {
  11. return true;
  12. }
  13. public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
  14. }
  15. public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
  16. }
  17. public void configureMessageBroker(MessageBrokerRegistry registry) {
  18. }
  19. }

WebSocketMessageBrokerConfigurer接口

  1. public interface WebSocketMessageBrokerConfigurer {
  2. // 添加这个Endpoint,这样在网页中就可以通过websocket连接上服务,也就是我们配置websocket的服务地址,并且可以指定是否使用socketjs
  3. void registerStompEndpoints(StompEndpointRegistry var1);
  4. // 配置发送与接收的消息参数,可以指定消息字节大小,缓存大小,发送超时时间
  5. void configureWebSocketTransport(WebSocketTransportRegistration var1);
  6. // 设置输入消息通道的线程数,默认线程为1,可以自己自定义线程数,最大线程数,线程存活时间
  7. void configureClientInboundChannel(ChannelRegistration var1);
  8. // 设置输出消息通道的线程数,默认线程为1,可以自己自定义线程数,最大线程数,线程存活时间
  9. void configureClientOutboundChannel(ChannelRegistration var1);
  10. // 添加自定义的消息转换器,spring 提供多种默认的消息转换器,返回false,不会添加消息转换器,返回true,会添加默认的消息转换器,当然也可以把自己写的消息转换器添加到转换链中
  11. boolean configureMessageConverters(List<MessageConverter> var1);
  12. // 配置消息代理,哪种路径的消息会进行代理处理
  13. void configureMessageBroker(MessageBrokerRegistry var1);
  14. // 自定义控制器方法的参数类型,有兴趣可以百度google HandlerMethodArgumentResolver这个的用法
  15. void addArgumentResolvers(List<HandlerMethodArgumentResolver> var1);
  16. // 自定义控制器方法返回值类型,有兴趣可以百度google HandlerMethodReturnValueHandler这个的用法
  17. void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> var1);
  18. }

在registerStompEndpoints 方法中,我们可以设置websocket服务的地址,同样,我们也可以根据自身业务需求,去添加拦截器,例如前文我们写的WebSocketHandshakeInterceptor拦截器,可以获取到httpsession,同样,当我们把信息存入map 后,都可以通过通过WebSocketSession的getAttributes()下提供get方法获取

  1. /**
  2. * 添加这个Endpoint,这样在网页中就可以通过websocket连接上服务,
  3. * 也就是我们配置websocket的服务地址,并且可以指定是否使用socketjs
  4. *
  5. * @param registry
  6. */
  7. @Override
  8. public void registerStompEndpoints(StompEndpointRegistry registry)
  9. {
  10. /*
  11. * 1. 将 /serviceName/stomp/websocketJs路径注册为STOMP的端点,
  12. * 用户连接了这个端点后就可以进行websocket通讯,支持socketJs
  13. * 2. setAllowedOrigins("*")表示可以跨域
  14. * 3. withSockJS()表示支持socktJS访问
  15. * 4. 添加自定义拦截器,这个拦截器是上一个demo自己定义的获取httpsession的拦截器
  16. */
  17. registry.addEndpoint("/stomp/websocketJS")
  18. .setAllowedOrigins("*")
  19. .withSockJS()
  20. .setInterceptors(new WebSocketHandshakeInterceptor())
  21. ;
  22. /*
  23. * 看了下源码,它的实现类是WebMvcStompEndpointRegistry ,
  24. * addEndpoint是添加到WebMvcStompWebSocketEndpointRegistration的集合中,
  25. * 所以可以添加多个端点
  26. */
  27. registry.addEndpoint("/stomp/websocket");
  28. }

如果我们业务关心,用户的数量,在线数量,连接状况等数据,我们也可以通过ChannelRegistration对象的setInterceptors方法添加监听,这里先展示一个完整的实现类,监听接口在后面会介绍,代码中的WebSocketHandshakeInterceptor 拦截器,是上一个例子已经实现的,用于存储httpsession,WebSocketChannelInterceptor 拦截器 ,在这个拦截器中可以做一些在线人数统计等操作,后面会介绍

  1. package com.wzh.demo.websocket.config;
  2. import com.wzh.demo.websocket.handler.MyPrincipalHandshakeHandler;
  3. import com.wzh.demo.websocket.interceptor.WebSocketChannelInterceptor;
  4. import com.wzh.demo.websocket.interceptor.WebSocketHandshakeInterceptor;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.messaging.converter.MessageConverter;
  8. import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
  9. import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
  10. import org.springframework.messaging.simp.config.ChannelRegistration;
  11. import org.springframework.messaging.simp.config.MessageBrokerRegistry;
  12. import org.springframework.scheduling.concurrent.DefaultManagedTaskScheduler;
  13. import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
  14. import org.springframework.util.AntPathMatcher;
  15. import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
  16. import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
  17. import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
  18. import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;
  19. import java.util.List;
  20. /**
  21. * <配置基于STOMP的websocket>
  22. * <功能详细描述>
  23. * @author wzh
  24. * @version 2018-08-12 18:38
  25. * @see [相关类/方法] (可选)
  26. **/
  27. @Configuration
  28. @EnableWebSocketMessageBroker
  29. public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
  30. /**
  31. * 添加这个Endpoint,这样在网页中就可以通过websocket连接上服务,
  32. * 也就是我们配置websocket的服务地址,并且可以指定是否使用socketjs
  33. *
  34. * @param registry
  35. */
  36. @Override
  37. public void registerStompEndpoints(StompEndpointRegistry registry)
  38. {
  39. /*
  40. * 1. 将 /serviceName/stomp/websocketJs路径注册为STOMP的端点,
  41. * 用户连接了这个端点后就可以进行websocket通讯,支持socketJs
  42. * 2. setAllowedOrigins("*")表示可以跨域
  43. * 3. withSockJS()表示支持socktJS访问
  44. * 4. addInterceptors 添加自定义拦截器,这个拦截器是上一个demo自己定义的获取httpsession的拦截器
  45. * 5. addInterceptors 添加拦截处理,这里MyPrincipalHandshakeHandler 封装的认证用户信息
  46. */
  47. registry.addEndpoint("/stomp/websocketJS")
  48. //.setAllowedOrigins("*")
  49. .addInterceptors(new WebSocketHandshakeInterceptor())
  50. .setHandshakeHandler(new MyPrincipalHandshakeHandler())
  51. .withSockJS()
  52. ;
  53. /*
  54. * 看了下源码,它的实现类是WebMvcStompEndpointRegistry ,
  55. * addEndpoint是添加到WebMvcStompWebSocketEndpointRegistration的集合中,
  56. * 所以可以添加多个端点
  57. */
  58. registry.addEndpoint("/stomp/websocket");
  59. }
  60. /**
  61. * 配置消息代理
  62. * @param registry
  63. */
  64. @Override
  65. public void configureMessageBroker(MessageBrokerRegistry registry)
  66. {
  67. /*
  68. * enableStompBrokerRelay 配置外部的STOMP服务,需要安装额外的支持 比如rabbitmq或activemq
  69. * 1. 配置代理域,可以配置多个,此段代码配置代理目的地的前缀为 /topicTest 或者 /userTest
  70. * 我们就可以在配置的域上向客户端推送消息
  71. * 3. 可以通过 setRelayHost 配置代理监听的host,默认为localhost
  72. * 4. 可以通过 setRelayPort 配置代理监听的端口,默认为61613
  73. * 5. 可以通过 setClientLogin 和 setClientPasscode 配置账号和密码
  74. * 6. setxxx这种设置方法是可选的,根据业务需要自行配置,也可以使用默认配置
  75. */
  76. //registry.enableStompBrokerRelay("/topicTest","/userTest")
  77. //.setRelayHost("rabbit.someotherserver")
  78. //.setRelayPort(62623);
  79. //.setClientLogin("userName")
  80. //.setClientPasscode("password")
  81. //;
  82. // 自定义调度器,用于控制心跳线程
  83. ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
  84. // 线程池线程数,心跳连接开线程
  85. taskScheduler.setPoolSize(1);
  86. // 线程名前缀
  87. taskScheduler.setThreadNamePrefix("websocket-heartbeat-thread-");
  88. // 初始化
  89. taskScheduler.initialize();
  90. /*
  91. * spring 内置broker对象
  92. * 1. 配置代理域,可以配置多个,此段代码配置代理目的地的前缀为 /topicTest 或者 /userTest
  93. * 我们就可以在配置的域上向客户端推送消息
  94. * 2,进行心跳设置,第一值表示server最小能保证发的心跳间隔毫秒数, 第二个值代码server希望client发的心跳间隔毫秒数
  95. * 3. 可以配置心跳线程调度器 setHeartbeatValue这个不能单独设置,不然不起作用,要配合setTaskScheduler才可以生效
  96. * 调度器我们可以自己写一个,也可以自己使用默认的调度器 new DefaultManagedTaskScheduler()
  97. */
  98. registry.enableSimpleBroker("/topicTest","/userTest")
  99. .setHeartbeatValue(new long[]{10000,10000})
  100. .setTaskScheduler(taskScheduler);
  101. /*
  102. * "/app" 为配置应用服务器的地址前缀,表示所有以/app 开头的客户端消息或请求
  103. * 都会路由到带有@MessageMapping 注解的方法中
  104. */
  105. registry.setApplicationDestinationPrefixes("/app");
  106. /*
  107. * 1. 配置一对一消息前缀, 客户端接收一对一消息需要配置的前缀 如“'/user/'+userid + '/message'”,
  108. * 是客户端订阅一对一消息的地址 stompClient.subscribe js方法调用的地址
  109. * 2. 使用@SendToUser发送私信的规则不是这个参数设定,在框架内部是用UserDestinationMessageHandler处理,
  110. * 而不是而不是 AnnotationMethodMessageHandler 或 SimpleBrokerMessageHandler
  111. * or StompBrokerRelayMessageHandler,是在@SendToUser的URL前加“user+sessionId"组成
  112. */
  113. registry.setUserDestinationPrefix("/user");
  114. /*
  115. * 自定义路径分割符
  116. * 注释掉的这段代码添加的分割符为. 分割是类级别的@messageMapping和方法级别的@messageMapping的路径
  117. * 例如类注解路径为 “topic”,方法注解路径为“hello”,那么客户端JS stompClient.send 方法调用的路径为“/app/topic.hello”
  118. * 注释掉此段代码后,类注解路径“/topic”,方法注解路径“/hello”,JS调用的路径为“/app/topic/hello”
  119. */
  120. //registry.setPathMatcher(new AntPathMatcher("."));
  121. }
  122. /**
  123. * 配置发送与接收的消息参数,可以指定消息字节大小,缓存大小,发送超时时间
  124. * @param registration
  125. */
  126. @Override
  127. public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
  128. /*
  129. * 1. setMessageSizeLimit 设置消息缓存的字节数大小 字节
  130. * 2. setSendBufferSizeLimit 设置websocket会话时,缓存的大小 字节
  131. * 3. setSendTimeLimit 设置消息发送会话超时时间,毫秒
  132. */
  133. registration.setMessageSizeLimit(10240)
  134. .setSendBufferSizeLimit(10240)
  135. .setSendTimeLimit(10000);
  136. }
  137. /**
  138. * 设置输入消息通道的线程数,默认线程为1,可以自己自定义线程数,最大线程数,线程存活时间
  139. * @param registration
  140. */
  141. @Override
  142. public void configureClientInboundChannel(ChannelRegistration registration) {
  143. /*
  144. * 配置消息线程池
  145. * 1. corePoolSize 配置核心线程池,当线程数小于此配置时,不管线程中有无空闲的线程,都会产生新线程处理任务
  146. * 2. maxPoolSize 配置线程池最大数,当线程池数等于此配置时,不会产生新线程
  147. * 3. keepAliveSeconds 线程池维护线程所允许的空闲时间,单位秒
  148. */
  149. registration.taskExecutor().corePoolSize(10)
  150. .maxPoolSize(20)
  151. .keepAliveSeconds(60);
  152. /*
  153. * 添加stomp自定义拦截器,可以根据业务做一些处理
  154. * springframework 4.3.12 之后版本此方法废弃,代替方法 interceptors(ChannelInterceptor... interceptors)
  155. * 消息拦截器,实现ChannelInterceptor接口
  156. */
  157. registration.setInterceptors(webSocketChannelInterceptor());
  158. }
  159. /**
  160. *设置输出消息通道的线程数,默认线程为1,可以自己自定义线程数,最大线程数,线程存活时间
  161. * @param registration
  162. */
  163. @Override
  164. public void configureClientOutboundChannel(ChannelRegistration registration) {
  165. registration.taskExecutor().corePoolSize(10)
  166. .maxPoolSize(20)
  167. .keepAliveSeconds(60);
  168. //registration.setInterceptors(new WebSocketChannelInterceptor());
  169. }
  170. /**
  171. * 添加自定义的消息转换器,spring 提供多种默认的消息转换器,
  172. * 返回false,不会添加消息转换器,返回true,会添加默认的消息转换器,当然也可以把自己写的消息转换器添加到转换链中
  173. * @param list
  174. * @return
  175. */
  176. @Override
  177. public boolean configureMessageConverters(List<MessageConverter> list) {
  178. return true;
  179. }
  180. /**
  181. * 自定义控制器方法的参数类型,有兴趣可以百度google HandlerMethodArgumentResolver这个的用法
  182. * @param list
  183. */
  184. @Override
  185. public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) {
  186. }
  187. /**
  188. * 自定义控制器方法返回值类型,有兴趣可以百度google HandlerMethodReturnValueHandler这个的用法
  189. * @param list
  190. */
  191. @Override
  192. public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) {
  193. }
  194. /**
  195. * 拦截器加入 spring ioc容器
  196. * @return
  197. */
  198. @Bean
  199. public WebSocketChannelInterceptor webSocketChannelInterceptor()
  200. {
  201. return new WebSocketChannelInterceptor();
  202. }
  203. }
WebSocketChannelInterceptor 的实现步骤

如果需要添加监听,我们的监听类需要实现ChannelInterceptor接口,在 springframework包5.0.7之前这一步我们一般是实现ChannelInterceptorAdapter 抽象类,不过这个类已经废弃了,文档也推荐直接实现接口。

 
image.png

首先我们看一下,ChannelInterceptor 哪些方法

  1. package org.springframework.messaging.support;
  2. import org.springframework.messaging.Message;
  3. import org.springframework.messaging.MessageChannel;
  4. public interface ChannelInterceptor {
  5. // 在消息发送之前调用,方法中可以对消息进行修改,如果此方法返回值为空,则不会发生实际的消息发送调用
  6. Message<?> preSend(Message<?> var1, MessageChannel var2);
  7. // 在消息发送后立刻调用,boolean值参数表示该调用的返回值
  8. void postSend(Message<?> var1, MessageChannel var2, boolean var3);
  9. /*
  10. * 1. 在消息发送完成后调用,而不管消息发送是否产生异常,在次方法中,我们可以做一些资源释放清理的工作
  11. * 2. 此方法的触发必须是preSend方法执行成功,且返回值不为null,发生了实际的消息推送,才会触发
  12. */
  13. void afterSendCompletion(Message<?> var1, MessageChannel var2, boolean var3, Exception var4);
  14. /* 1. 在消息被实际检索之前调用,如果返回false,则不会对检索任何消息,只适用于(PollableChannels),
  15. * 2. 在websocket的场景中用不到
  16. */
  17. boolean preReceive(MessageChannel var1);
  18. /*
  19. * 1. 在检索到消息之后,返回调用方之前调用,可以进行信息修改,如果返回null,就不会进行下一步操作
  20. * 2. 适用于PollableChannels,轮询场景
  21. */
  22. Message<?> postReceive(Message<?> var1, MessageChannel var2);
  23. /*
  24. * 1. 在消息接收完成之后调用,不管发生什么异常,可以用于消息发送后的资源清理
  25. * 2. 只有当preReceive 执行成功,并返回true才会调用此方法
  26. * 2. 适用于PollableChannels,轮询场景
  27. */
  28. void afterReceiveCompletion(Message<?> var1, MessageChannel var2, Exception var3);
  29. }

上面有说道,在ChannelInterceptor接口中的preSend能在消息发送前做一些处理,例如可以获取到用户登录的唯一token令牌,这里的令牌是我们业务传递给客户端的,例如用户在登录成功后跳转到websocket建立连接的页面,我们后台生成的一个标识符,客户端在和服务端建立websocket连接的时候,我们可以从消息头中获取到这种业务参数,并做一系列后续处理,如果要做这种业务操作,我们还需要一个Authentication对象,这个对象是我们自己写的,这个类必须实现java.security.Principal,这里只是做一个简单的token存储,可以根据实际的业务 逻辑进行扩展。

  1. import java.security.Principal;
  2. /**
  3. * <websocket登录连接对象>
  4. * <用于保存websocket连接过程中需要存储的业务参数>
  5. * @author wzh
  6. * @version 2018-08-26 23:30
  7. * @see [相关类/方法] (可选)
  8. **/
  9. public class WebSocketUserAuthentication implements Principal{
  10. /**
  11. * 用户身份标识符
  12. */
  13. private String token;
  14. public WebSocketUserAuthentication(String token) {
  15. this.token = token;
  16. }
  17. public WebSocketUserAuthentication() {
  18. }
  19. /**
  20. * 获取用户登录令牌
  21. * @return
  22. */
  23. @Override
  24. public String getName() {
  25. return token;
  26. }
  27. }

一个消息头拦截器,用于获取用户的认证信息


  1. package com.wzh.demo.websocket.handler;
  2. import com.wzh.demo.domain.WebSocketUserAuthentication;
  3. import org.apache.commons.lang.StringUtils;
  4. import org.apache.log4j.Logger;
  5. import org.springframework.http.server.ServerHttpRequest;
  6. import org.springframework.http.server.ServletServerHttpRequest;
  7. import org.springframework.web.socket.WebSocketHandler;
  8. import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
  9. import javax.servlet.http.HttpSession;
  10. import java.security.Principal;
  11. import java.util.Map;
  12. /**
  13. * <设置认证用户信息>
  14. * <功能详细描述>
  15. * @author wzh
  16. * @version 2018-09-18 23:55
  17. * @see [相关类/方法] (可选)
  18. **/
  19. public class MyPrincipalHandshakeHandler extends DefaultHandshakeHandler{
  20. private static final Logger log = Logger.getLogger(MyPrincipalHandshakeHandler.class);
  21. @Override
  22. protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
  23. HttpSession httpSession = getSession(request);
  24. // 获取登录的信息,就是controller 跳转页面存的信息,可以根据业务修改
  25. String user = (String)httpSession.getAttribute("loginName");
  26. if(StringUtils.isEmpty(user)){
  27. log.error("未登录系统,禁止登录websocket!");
  28. return null;
  29. }
  30. log.info(" MyDefaultHandshakeHandler login = " + user);
  31. return new WebSocketUserAuthentication(user);
  32. }
  33. private HttpSession getSession(ServerHttpRequest request) {
  34. if (request instanceof ServletServerHttpRequest) {
  35. ServletServerHttpRequest serverRequest = (ServletServerHttpRequest) request;
  36. return serverRequest.getServletRequest().getSession(false);
  37. }
  38. return null;
  39. }
  40. }

下面我们做个拦截器,在preSend方法中获取封装首次登陆后的令牌信息,在postSend方法中统计在线人数

WebSocketChannelInterceptor 拦截登录时消息头中的信息
  1. package com.wzh.demo.websocket.interceptor;
  2. import com.wzh.demo.domain.WebSocketUserAuthentication;
  3. import org.apache.log4j.Logger;
  4. import org.springframework.messaging.Message;
  5. import org.springframework.messaging.MessageChannel;
  6. import org.springframework.messaging.simp.stomp.StompCommand;
  7. import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
  8. import org.springframework.messaging.support.ChannelInterceptor;
  9. import org.springframework.messaging.support.MessageHeaderAccessor;
  10. import javax.servlet.http.HttpSession;
  11. import static org.springframework.messaging.simp.stomp.StompCommand.CONNECT;
  12. /**
  13. * <websocke消息监听,用于监听websocket用户连接情况>
  14. * <功能详细描述>
  15. * @author wzh
  16. * @version 2018-08-25 23:39
  17. * @see [相关类/方法] (可选)
  18. **/
  19. public class WebSocketChannelInterceptor implements ChannelInterceptor {
  20. public WebSocketChannelInterceptor() {
  21. }
  22. Logger log = Logger.getLogger(WebSocketChannelInterceptor.class);
  23. // 在消息发送之前调用,方法中可以对消息进行修改,如果此方法返回值为空,则不会发生实际的消息发送调用
  24. @Override
  25. public Message<?> preSend(Message<?> message, MessageChannel messageChannel) {
  26. StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
  27. /**
  28. * 1. 判断是否为首次连接请求,如果已经连接过,直接返回message
  29. * 2. 网上有种写法是在这里封装认证用户的信息,本文是在http阶段,websockt 之前就做了认证的封装,所以这里直接取的信息
  30. */
  31. if(StompCommand.CONNECT.equals(accessor.getCommand()))
  32. {
  33. /*
  34. * 1. 这里获取就是JS stompClient.connect(headers, function (frame){.......}) 中header的信息
  35. * 2. JS中header可以封装多个参数,格式是{key1:value1,key2:value2}
  36. * 3. header参数的key可以一样,取出来就是list
  37. * 4. 样例代码header中只有一个token,所以直接取0位
  38. */
  39. String token = accessor.getNativeHeader("token").get(0);
  40. /*
  41. * 1. 这里直接封装到StompHeaderAccessor 中,可以根据自身业务进行改变
  42. * 2. 封装大搜StompHeaderAccessor中后,可以在@Controller / @MessageMapping注解的方法中直接带上StompHeaderAccessor
  43. * 就可以通过方法提供的 getUser()方法获取到这里封装user对象
  44. * 2. 例如可以在这里拿到前端的信息进行登录鉴权
  45. */
  46. WebSocketUserAuthentication user = (WebSocketUserAuthentication) accessor.getUser();
  47. System.out.println("认证用户:" + user.toString() + " 页面传递令牌" + token);
  48. }else if (StompCommand.DISCONNECT.equals(accessor.getCommand()))
  49. {
  50. }
  51. return message;
  52. }
  53. // 在消息发送后立刻调用,boolean值参数表示该调用的返回值
  54. @Override
  55. public void postSend(Message<?> message, MessageChannel messageChannel, boolean b) {
  56. StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
  57. /*
  58. * 拿到消息头对象后,我们可以做一系列业务操作
  59. * 1. 通过getSessionAttributes()方法获取到websocketSession,
  60. * 就可以取到我们在WebSocketHandshakeInterceptor拦截器中存在session中的信息
  61. * 2. 我们也可以获取到当前连接的状态,做一些统计,例如统计在线人数,或者缓存在线人数对应的令牌,方便后续业务调用
  62. */
  63. HttpSession httpSession = (HttpSession) accessor.getSessionAttributes().get("HTTP_SESSION");
  64. // 这里只是单纯的打印,可以根据项目的实际情况做业务处理
  65. log.info("postSend 中获取httpSession key:" + httpSession.getId());
  66. // 忽略心跳消息等非STOMP消息
  67. if(accessor.getCommand() == null)
  68. {
  69. return;
  70. }
  71. // 根据连接状态做处理,这里也只是打印了下,可以根据实际场景,对上线,下线,首次成功连接做处理
  72. System.out.println(accessor.getCommand());
  73. switch (accessor.getCommand())
  74. {
  75. // 首次连接
  76. case CONNECT:
  77. log.info("httpSession key:" + httpSession.getId() + " 首次连接");
  78. break;
  79. // 连接中
  80. case CONNECTED:
  81. break;
  82. // 下线
  83. case DISCONNECT:
  84. log.info("httpSession key:" + httpSession.getId() + " 下线");
  85. break;
  86. default:
  87. break;
  88. }
  89. }
  90. /*
  91. * 1. 在消息发送完成后调用,而不管消息发送是否产生异常,在次方法中,我们可以做一些资源释放清理的工作
  92. * 2. 此方法的触发必须是preSend方法执行成功,且返回值不为null,发生了实际的消息推送,才会触发
  93. */
  94. @Override
  95. public void afterSendCompletion(Message<?> message, MessageChannel messageChannel, boolean b, Exception e) {
  96. }
  97. /* 1. 在消息被实际检索之前调用,如果返回false,则不会对检索任何消息,只适用于(PollableChannels),
  98. * 2. 在websocket的场景中用不到
  99. */
  100. @Override
  101. public boolean preReceive(MessageChannel messageChannel) {
  102. return true;
  103. }
  104. /*
  105. * 1. 在检索到消息之后,返回调用方之前调用,可以进行信息修改,如果返回null,就不会进行下一步操作
  106. * 2. 适用于PollableChannels,轮询场景
  107. */
  108. @Override
  109. public Message<?> postReceive(Message<?> message, MessageChannel messageChannel) {
  110. return message;
  111. }
  112. /*
  113. * 1. 在消息接收完成之后调用,不管发生什么异常,可以用于消息发送后的资源清理
  114. * 2. 只有当preReceive 执行成功,并返回true才会调用此方法
  115. * 2. 适用于PollableChannels,轮询场景
  116. */
  117. @Override
  118. public void afterReceiveCompletion(Message<?> message, MessageChannel messageChannel, Exception e) {
  119. }
  120. }

服务端发送消息大体有两种场景,公告和私信,实现的方式蛮多的,这里只是举例说明,具体可以看此篇博文。

路径:https://blog.csdn.net/pacosonswjtu/article/details/51914567

服务端处理消息的场景:

  1. 公告就是只要订阅了此路径的的用户都能收到,我们使用@SendTo 注解实现,如果不使用注解指定,

    1. 会默认交给broker进行处理,例如@MessageMapping("/demo1/twoWays") 这种,就会拼接代理域+路径
    2. 相当于配置了@SendTo("/topicTest/twoWays"),也可以使用SimpMessagingTemplate.convertAndSend
  2. 私信就是指定人员才能收到,可以用@SendToUser 注解或者SimpMessagingTemplate 模板类(框架提供)的convertAndSendToUser进行处理

    • @SendToUser 多用于资源的请求,如果我只是想简单的用websocket向服务器请求资源而已,然后服务器你就把资源给我就行了,别的用户就不用你广播推送了,简单点,就是我请求,你就推送给我
    • SimpMessagingTemplate.convertAndSendToUser 可以用户发送指定的人员
    • 使用指定人员发送的时候,前缀必须为配置的setUserDestinationPrefix 配置的“/user”,在spring 框架内部以"/user" 为前缀的消息将会通过 UserDestinationMessageHandler 进行处理,而不是 AnnotationMethodMessageHandler 或 SimpleBrokerMessageHandler or StompBrokerRelayMessageHandler。UserDestinationMessageHandler 的主要任务: 是 将用户消息重新路由到 某个用户独有的目的地上。 在处理订阅的时候,它会将目标地址中的 "/user" 前缀去掉,并基于用户 的会话添加一个后缀。如,对 "/user/userTest/notifications" 的订阅最后可能路由到 名为 "/userTest/notifacations-user65a4sdfa" 目的地上

服务端controller 用于接收客户端消息和响应客户端

  1. package com.wzh.demo.controller;
  2. import com.alibaba.fastjson.JSON;
  3. import com.wzh.demo.domain.WebSocketUserAuthentication;
  4. import org.apache.log4j.Logger;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.messaging.handler.annotation.DestinationVariable;
  7. import org.springframework.messaging.handler.annotation.MessageMapping;
  8. import org.springframework.messaging.handler.annotation.SendTo;
  9. import org.springframework.messaging.simp.SimpMessagingTemplate;
  10. import org.springframework.messaging.simp.annotation.SendToUser;
  11. import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
  12. import org.springframework.stereotype.Controller;
  13. import java.util.HashMap;
  14. import java.util.Map;
  15. /**
  16. * <STOMP websocket controller>
  17. * <功能详细描述>
  18. * @author wzh
  19. * @version 2018-08-29 22:58
  20. * @see [相关类/方法] (可选)
  21. **/
  22. @Controller
  23. public class WebSocketStompController {
  24. Logger log = Logger.getLogger(WebSocketStompController.class);
  25. private final SimpMessagingTemplate messagingTemplate;
  26. /**
  27. * 实例化Controller的时候,注入SimpMessagingTemplate
  28. * @param messagingTemplate
  29. */
  30. @Autowired
  31. public WebSocketStompController(SimpMessagingTemplate messagingTemplate)
  32. {
  33. this.messagingTemplate = messagingTemplate;
  34. }
  35. /**
  36. * 发送广播消息,所有订阅了此路径的用户都会收到此消息
  37. * 这里做个restful风格,其实无所谓,根据项目实际情况进行配置
  38. * restful风格的接口,在springMVC中,我们使用@PathVariable注解,
  39. * 在websocket stomp接口中,restful要使用@DestinationVariable
  40. * @param groupId
  41. * @param json
  42. * @param headerAccessor
  43. * @return
  44. */
  45. @MessageMapping("/sendChatMsg/{groupId}")
  46. @SendTo("/topicTest/hello")
  47. public Map<String, Object> sendChatMsg(@DestinationVariable(value = "groupId") String groupId, String json,
  48. StompHeaderAccessor headerAccessor)
  49. {
  50. // 这里拿到的user对象是在WebSocketChannelInterceptor拦截器中绑定上的对象
  51. WebSocketUserAuthentication user =(WebSocketUserAuthentication)headerAccessor.getUser();
  52. log.info("公告controller 中获取用户登录令牌:" + user.getName());
  53. log.info("公告拿到客户端传递分组参数:" + groupId);
  54. // 这里拿到的json 字符串,其实可以自动绑定到对象上
  55. System.out.println("公告获取客户端传递过来的JSON 字符串:" + json);
  56. Map msg = (Map) JSON.parse(json);
  57. Map<String, Object> data = new HashMap<String, Object>();
  58. data.put("msg", "公告服务器收到客户端请求,发送广播消息:"+ msg.get("msg"));
  59. return data;
  60. }
  61. /**
  62. * 发送私信消息,只是想简单的用websocket向服务器请求资源而已,
  63. * 然后服务器你就把资源给我就行了,别的用户就不用你广播推送了,简单点,就是我请求,你就推送给我
  64. * 如果一个帐号打开了多个浏览器窗口,也就是打开了多个websocket session通道,
  65. * 这时,spring webscoket默认会把消息推送到同一个帐号不同的session,
  66. * 可以利用broadcast = false把避免推送到所有的session中
  67. * @param json
  68. * @param headerAccessor
  69. * @return
  70. */
  71. @MessageMapping("/sendChatMsgByOwn")
  72. @SendToUser(value = "/userTest/own")
  73. public Map<String, Object> sendChatMsgByOwn(String json,
  74. StompHeaderAccessor headerAccessor)
  75. {
  76. // 这里拿到的user对象是在WebSocketChannelInterceptor拦截器中绑定上的对象
  77. WebSocketUserAuthentication user = (WebSocketUserAuthentication)headerAccessor.getUser();
  78. log.info("SendToUser controller 中获取用户登录令牌:" + user.getName()
  79. + " socketId:" + headerAccessor.getSessionId());
  80. // 这里拿到的json 字符串,其实可以自动绑定到对象上
  81. System.out.println("SendToUser获取客户端传递过来的JSON 字符串:" + json);
  82. Map msg = (Map)JSON.parse(json);
  83. Map<String, Object> data = new HashMap<String, Object>();
  84. data.put("msg", "SendToUser服务器收到客户端请求,发送私信消息:" + msg.get("msg"));
  85. return data;
  86. }
  87. /**
  88. * 根据ID 把消息推送给指定用户
  89. * 1. 这里用了 @SendToUser 和 返回值 其意义是可以在发送成功后回执给发送放其信息发送成功
  90. * 2. 非必须,如果实际业务不需要关心此,可以不用@SendToUser注解,方法返回值为void
  91. * 3. 这里接收人的参数是用restful风格带过来了,websocket把参数带到后台的方式很多,除了url路径,
  92. * 还可以在header中封装用@Header或者@Headers去取等多种方式
  93. * @param accountId 消息接收人ID
  94. * @param json 消息JSON字符串
  95. * @param headerAccessor
  96. * @return
  97. */
  98. @MessageMapping("/sendChatMsgById/{accountId}")
  99. @SendToUser(value = "/userTest/callBack")
  100. public Map<String, Object> sendChatMsgById(
  101. @DestinationVariable(value = "accountId") String accountId, String json,
  102. StompHeaderAccessor headerAccessor)
  103. {
  104. Map msg = (Map)JSON.parse(json);
  105. Map<String, Object> data = new HashMap<String, Object>();
  106. // 这里拿到的user对象是在WebSocketChannelInterceptor拦截器中绑定上的对象
  107. WebSocketUserAuthentication user = (WebSocketUserAuthentication)headerAccessor.getUser();
  108. log.info("SendToUser controller 中获取用户登录令牌:" + user.getName()
  109. + " socketId:" + headerAccessor.getSessionId());
  110. // 向用户发送消息,第一个参数是接收人、第二个参数是浏览器订阅的地址,第三个是消息本身
  111. // 如果服务端要将消息发送给特定的某一个用户,
  112. // 可以使用SimpleMessageTemplate的convertAndSendToUser方法(第一个参数是用户的登陆名username)
  113. String address = "/userTest/callBack";
  114. messagingTemplate.convertAndSendToUser(accountId, address, msg.get("msg"));
  115. data.put("msg", "callBack 消息已推送,消息内容:" + msg.get("msg"));
  116. return data;
  117. }
  118. }

一个springMVC的controller 用户跳转websocket页面,并封装简单的认证信息

  1. package com.wzh.demo.controller;
  2. import com.wzh.demo.websocket.handler.WebSocketHander;
  3. import org.springframework.boot.autoconfigure.web.ServerProperties;
  4. import org.springframework.stereotype.Controller;
  5. import org.springframework.ui.Model;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RequestMethod;
  8. import org.springframework.web.socket.TextMessage;
  9. import javax.annotation.Resource;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpSession;
  12. import java.util.Date;
  13. /**
  14. * <websocket测试用MVC控制器>
  15. * <功能详细描述>
  16. * @author wzh
  17. * @version 2018-07-09 22:53
  18. * @see [相关类/方法] (可选)
  19. **/
  20. @Controller
  21. @RequestMapping("/websocket")
  22. public class WebSocketController {
  23. // 跳转stomp websocket 页面
  24. @RequestMapping(value = "/spring/stompSocket.do",method = RequestMethod.GET)
  25. public String toStompWebSocket(HttpSession session, HttpServletRequest request, Model model)
  26. {
  27. // 这里封装一个登录的用户组参数,模拟进入通讯后的简单初始化
  28. model.addAttribute("groupId","user_groupId");
  29. model.addAttribute("session_id",session.getId());
  30. System.out.println("跳转:" + session.getId());
  31. session.setAttribute("loginName",session.getId());
  32. return "/test/springWebSocketStomp";
  33. }
  34. }

Html 客户端,客户端需要引入额外的两个js,sockjs.js和stomp.js

Github 地址:

API地址:https://stomp-js.github.io/stomp-websocket/codo/extra/docs-src/Usage.md.html

API中文翻译博文:https://blog.csdn.net/jqsad/article/details/77745379


  1. <#import "spring.ftl" as spring />
  2. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  3. "http://www.w3.org/TR/html4/loose.dtd">
  4. <html>
  5. <head>
  6. <title>Title</title>
  7. <script src="${request.contextPath}/js/jquery-3.3.1.min.js"></script>
  8. <script src="${request.contextPath}/js/sockjs.js"></script>
  9. <script src="${request.contextPath}/js/stomp.js"></script>
  10. <script type="text/javascript">
  11. // 定义全局变量 stomp socket
  12. var stompClient,socket;
  13. $(document).ready(function () {
  14. if (window.WebSocket){
  15. websocketConfig();
  16. } else {
  17. alert("错误","浏览器不支持websocket技术通讯.");
  18. }
  19. });
  20. // websocket 配置
  21. function websocketConfig() {
  22. /*
  23. * 1. 连接url为endpointChat的endpoint,对应后台WebSoccketConfig的配置
  24. * 2. SockJS 所处理的URL 是 "http://" 或 "https://" 模式,而不是 "ws://" or "wss://"
  25. */
  26. socket = new SockJS("${request.contextPath}/stomp/websocketJS");
  27. // 通过sock对象监听每个事件节点,非必须,这个必须放在stompClient的方法前面
  28. sockHandle();
  29. // 获取 STOMP 子协议的客户端对象
  30. stompClient = Stomp.over(socket);
  31. /*
  32. * 1. 获取到stomp 子协议后,可以设置心跳连接时间,认证连接,主动断开连接
  33. * 2,连接心跳有的版本的stomp.js 是默认开启的,这里我们不管版本,手工设置
  34. * 3. 心跳是双向的,客户端开启心跳,必须要服务端支持心跳才行
  35. * 4. heartbeat.outgoing 表示客户端给服务端发送心跳的间隔时间
  36. * 5. 客户端接收服务端心跳的间隔时间,如果为0 表示客户端不接收服务端心跳
  37. */
  38. stompClient.heartbeat.outgoing = 10000;
  39. stompClient.heartbeat.incoming = 0;
  40. /*
  41. * 1. stompClient.connect(headers, connectCallback, errorCallback);
  42. * 2. headers表示客户端的认证信息,多个参数 json格式存,这里简单用的httpsessionID,可以根据业务场景变更
  43. * 这里存的信息,在服务端StompHeaderAccessor 对象调用方法可以取到
  44. * 3. connectCallback 表示连接成功时(服务器响应 CONNECTED 帧)的回调方法;
  45. * errorCallback 表示连接失败时(服务器响应 ERROR 帧)的回调方法,非必须;
  46. */
  47. var headers = {token:"${session_id}"};
  48. stompClient.connect(headers,function (frame) {
  49. console.log('Connected: ' + frame);
  50. /*
  51. * 1. 订阅服务,订阅地址为服务器Controller 中的地址
  52. * 2. 如果订阅为公告,地址为Controller 中@SendTo 注解地址
  53. * 3. 如果订阅为私信,地址为setUserDestinationPrefix 前缀+@SendToUser注解地址
  54. * 或者setUserDestinationPrefix 前缀 + controller的convertAndSendToUser地址一致
  55. * 4. 这里演示为公告信息,所有订阅了的用户都能接受
  56. */
  57. stompClient.subscribe("/topicTest/hello",function (message) {
  58. var msg = JSON.parse(message.body).msg;
  59. console.log("接收到公告信息:" + msg);
  60. alert("接收到公告信息:" + msg);
  61. });
  62. /*
  63. * 1. 因为推送为私信,必须带上或者setUserDestinationPrefix前缀 /user
  64. * 2. 演示自己发送给自己,做websocket向服务器请求资源而已,然后服务器你就把资源给我就行了,
  65. * 别的用户就不用你广播推送了,简单点,就是我请求,你就推送给我
  66. */
  67. stompClient.subscribe('/user/userTest/own',function (message) {
  68. var msg = JSON.parse(message.body).msg;
  69. console.log("接收到私信信息SendToUser:" + msg);
  70. alert("接收到私信信息SendToUser:" + msg);
  71. });
  72. /*
  73. * 1. 订阅点对点消息
  74. * 2. 很多博文这里的路径会写成"/user/{accountId}/userTest/callBack”这种,是因为
  75. * @SendToUser发送的代理地址是 /userTest/callBack, 地址将会被转化为 /user/{username}/userTest/callBack
  76. * username,为用户的登录名,也是就是Principal或者本文中的WebSocketUserAuthentication对象getName获取的参数
  77. * 如果在拦截器中配置了认证路径,可以不带参数,不过推荐用带参数的写法
  78. *
  79. */
  80. stompClient.subscribe('/user/userTest/callBack',function (message) {
  81. var msg = message.body;
  82. console.log("接收到点对点SendToUser:" + msg);
  83. alert("接收到点对点SendToUser:" + msg);
  84. });
  85. }, function (error) {
  86. console.log('STOMP: ' + error);
  87. //setTimeout(websocketConfig, 10000);
  88. console.log('STOMP: Reconnecting in 10 seconds');
  89. });
  90. }
  91. // 发送公告消息
  92. function sendMsg() {
  93. var msg = $("#message").val();
  94. var data ={"msg":msg};
  95. /**
  96. * 1. 第一个参数 url 为服务器 controller中 @MessageMapping 中匹配的URL,字符串,必须参数;
  97. * 2. headers 为发送信息的header,json格式,JavaScript 对象,
  98. * 可选参数,可以携带消息头信息,也可以做事务,如果没有,传{}
  99. * 3. body 为发送信息的 body,字符串,可选参数
  100. */
  101. stompClient.send('${"/app/sendChatMsg/" + groupId}',{},JSON.stringify(data));
  102. }
  103. // 发送给自己
  104. function sendMsgOwn() {
  105. var msg = $("#message").val();
  106. var data ={"msg":msg};
  107. /**
  108. * 1. 第一个参数 url 为服务器 controller中 @MessageMapping 中匹配的URL,字符串,必须参数;
  109. * 2. headers 为发送信息的header,json格式,JavaScript 对象,
  110. * 可选参数,可以携带消息头信息,也可以做事务,如果没有,传{}
  111. * 3. body 为发送信息的 body,字符串,可选参数
  112. */
  113. stompClient.send("/app/sendChatMsgByOwn",{},JSON.stringify(data));
  114. }
  115. // 发送点对点消息
  116. function sendMsgById() {
  117. var msg = $("#message").val();
  118. var accountId = $("#accountId").val();
  119. var data ={"msg":msg};
  120. /**
  121. * 1. 第一个参数 url 为服务器 controller中 @MessageMapping 中匹配的URL,字符串,必须参数;
  122. * 2. headers 为发送信息的header,json格式,JavaScript 对象,
  123. * 可选参数,可以携带消息头信息,也可以做事务,如果没有,传{}
  124. * 3. body 为发送信息的 body,字符串,可选参数
  125. * 4. accountId这个参数其实可以通过header传过去,不过因为是restful风格,所以就跟在url上
  126. */
  127. stompClient.send("/app/sendChatMsgById/" + accountId,{},JSON.stringify(data));
  128. }
  129. // 通过sock对象监听每个事件节点,非必须,这里开启了stomp的websocket 也不会生效了
  130. function sockHandle() {
  131. // 连接成功后的回调函数
  132. socket.onopen = function () {
  133. console.log("------连接成功------");
  134. };
  135. // 监听接受到服务器的消息
  136. socket.onmessage = function (event) {
  137. console.log('-------收到的消息: ' + event.data);
  138. };
  139. // 关闭连接的回调函数
  140. socket.onclose = function (event) {
  141. console.log('--------关闭连接: connection closed.------');
  142. };
  143. // 连接发生错误
  144. socket.onerror = function () {
  145. alert("连接错误", "网络超时或通讯地址错误.");
  146. disconnect();
  147. } ;
  148. }
  149. // 关闭websocket
  150. function disconnect() {
  151. if (socket != null) {
  152. socket.close();
  153. socket = null;
  154. }
  155. }
  156. </script>
  157. </head>
  158. <body>
  159. <div>
  160. <span>消息</span>
  161. <input type="text" id="message" name="message">
  162. <input type="button" id="sendMsg" name="sendMsg" value="发送公告" onclick="sendMsg();">
  163. <input type="button" id="sendMsgOwn" name="sendMsgOwn" value="自己给自己推送" onclick="sendMsgOwn();">
  164. <br/>
  165. <span>接收人</span>
  166. <input type="text" id="accountId" name="accountId">
  167. <input type="button" id="sendMsgById" name="sendMsgById" value="点对点消息" onclick="sendMsgById();">
  168. </div>
  169. </body>
  170. </html>

这样就可以通过页面做简单的消息推送了

 
20180919224511380 (1).gif

websocket学习(转载)的更多相关文章

  1. WebSocket学习笔记——无痛入门

    WebSocket学习笔记——无痛入门 标签: websocket 2014-04-09 22:05 4987人阅读 评论(1) 收藏 举报  分类: 物联网学习笔记(37)  版权声明:本文为博主原 ...

  2. Java多线程学习(转载)

    Java多线程学习(转载) 时间:2015-03-14 13:53:14      阅读:137413      评论:4      收藏:3      [点我收藏+] 转载 :http://blog ...

  3. WebSocket学习笔记IE,IOS,Android等设备的兼容性问

    WebSocket学习笔记IE,IOS,Android等设备的兼容性问 一.背景 公司最近准备将一套产品放到Andriod和IOS上面去,为了统一应用的开发方式,决定用各平台APP嵌套一个HTML5浏 ...

  4. WebSocket 学习笔记

    WebSocket 学习笔记 来自我的博客 因为项目原因需要用到双工通信,所以比较详细的学习了一下浏览器端支持的 WebSocket. 并记录一些遇到的问题. 简介 WebSocket 一般是指浏览器 ...

  5. 【转载】Websocket学习

    首先是在Tomcat里面看到Websocket的演示.很有意思. http://localhost:8080/examples/websocket/index.xhtml 里面有: Echo exam ...

  6. websocket学习和群聊实现

    WebSocket协议可以实现前后端全双工通信,从而取代浪费资源的长轮询.在此协议的基础上,可以实现前后端数据.多端数据,真正的实时响应.在学习WebSocket的过程中,实现了一个简化版群聊,过程和 ...

  7. WebSocket 学习(三)--用nodejs搭建服务器

    前面已经学习了WebSocket API,包括事件.方法和属性.详情:WebSocket(二)--API  WebSocket是基于事件驱动,支持全双工通信.下面通过三个简单例子体验一下. 简单开始 ...

  8. WebSocket学习总结

    一 .websocket 已解决      但是websocket延伸出来的网络编程还有好多知识点没有清理.主要的流程和实现方式已经大概了解清楚,下面从学习的进度思路来一点点复习.        网络 ...

  9. java websocket学习

    引言: websocket,webservice傻傻分不清楚,都觉得是很高深的东西,理解中的webservice是一种协议,通信协议,类似http协议的那种,比如使用webservice协议调后台接口 ...

随机推荐

  1. PHP str_word_count() 函数

    实例 计算字符串 "Hello World!" 中的单词数: <?php高佣联盟 www.cgewang.comecho str_word_count("Hello ...

  2. Python Cookbook(第3版) 中文版 pdf完整版|网盘下载内附提取码

    Python Cookbook(第3版)中文版介绍了Python应用在各个领域中的一些使用技巧和方法,其主题涵盖了数据结构和算法,字符串和文本,数字.日期和时间,迭代器和生成器,文件和I/O,数据编码 ...

  3. bzoj 2839 集合计数 容斥\广义容斥

    LINK:集合计数 容斥简单题 却引出我对广义容斥的深思. 一直以来我都不理解广义容斥是为什么 在什么情况下使用. 给一张图: 这张图想要表达的意思就是这道题目的意思 而求的东西也和题目一致. 特点: ...

  4. 剑指 Offer 50. 第一个只出现一次的字符

    本题 题目链接 题目描述 我的题解 (方法三应用更广泛:方法一虽有限制,但很好用,此题中该方法效率也最高) 方法一:(适用于范围确定的) 思路分析 该字符串只包含小写字母,即字符种类最多26个 开一个 ...

  5. IDEA生成MyBatis文件

    IDEA 逆向 MyBatis 工程时,不像支持 Hibernate 那样有自带插件,需要集成第三方的 MyBatis Generator. MyBatis Generator的详细介绍 http:/ ...

  6. Spring Joinpoint

    如果用maven管理 则需要 <artifactId> aopalliance </artifactId> <artifactId> spring-aspects ...

  7. python 爬虫刷访问量

    import urllib.requestimport time # 使用build_opener()是为了让python程序模仿浏览器进行访问opener = urllib.request.buil ...

  8. JVM初探(五):类的实例化

    一.概述 我们知道,一个对象在可以被使用之前必须要被正确地实例化.而实例化实际指的就是以一个java类为模板创建对象/实例的过程.比如说常见的 Person = new Person()代码就是一个将 ...

  9. JQuery的turn.js实现翻书效果

    前言: hello大家好~好久没更博了……今天来和大家分享下JQ的turn.js,下面我先来简单介绍下我们今天的主角turn.js. Turn.js是一个JavaScript库,它集合了HTML5的所 ...

  10. JavaScript 手写setTimeout

    let setTimeout = (sec, num) => { // 初始当前时间 const now = new Date().getTime() let flag = true let c ...