# spring-session-sample-boot-websocket **Repository Path**: zsljava/spring-session-sample-boot-websocket ## Basic Information - **Project Name**: spring-session-sample-boot-websocket - **Description**: spring 官方 websocket demo - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-10-28 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # websocket stomp协议 实例demo > 为测试websocket的功能、稳定性、调优而创立本项目。主要用于websocket功能的测试 > WebSocket是HTML5新出的协议支持长久链接(http1.0短链接,http1.1长连接,websocket持久链接)。 在http中一个request对应一个response,且response是被动的,不能主动发起,而websocket中借用了HTTP的协议来完成一部分握手,建立连接后可以实现双向通话即只需要经过一次HTTP请求,就可以做到源源不断的信息传送了 ## websocket与http http1.0:短连接,通信由客户端发起,每次请求都需要与服务端重新建立一个tcp链接 http1.1: 长连接(复用链接),通信由客户端发起,当出现对服务器后继请求时,减少了建立和关闭连接的消耗和延迟。在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点 websocket: 持久链接,双向通信, client利用http来建立tcp连接,协议标识符是ws(如果加密,则为wss) ## 如何利用http建立websocket请求 1、页面使用SockJS ![SockJS](docs/images/1.png) 2、加载请求 (握手) ![SockJS](docs/images/2.png) ``` status code: 101 表示协议切换 Sec-WebSocket-Extensions: 用于对websocket协议进行扩展. 比如websocket协议本身不支持压缩, 但可以通过Sec-WebSocket-Extensions中的permessage-deflate来协商压缩。 Sec-WebSocket-Key:client随机生成的一段key。详见Sec-WebSocket-Accept处 Sec-WebSocket-Version:协议的版本号。 Upgrade:通过http的Upgrade对协议进行切换. 告诉server, 建立连接后用websocket协议。 其他参数为http已有参数,可以查询http相关资料了解 ``` 3、请求的返回结果 ![SockJS](docs/images/3.png) ``` Sec-WebSocket-Accept: server会根据request中的Sec-WebSocket-Key的值来生成response中的Sec-WebSocket-Accept的值。 具体的算法是根据Sec-WebSocket-Key与协议中已定义的一个guid “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”进行拼接, 再对结果进行sha1, 再对sha1的结果进行base64, 最后得到Sec-WebSocket-Accept的值. 目的: 验证服务端是否支持websocket协议, 防止服务端返回缓存的response ``` 4、stomp协议 ``` stomp是一个用于client之间进行异步消息传输的简单文本协议, 全称是Simple Text Oriented Messaging Protocol。 stomp协议并不是为websocket设计的, 它是属于消息队列的一种协议。常见的rabbitmq, activemq都支持该协议。 stomp协议中的client分为两角色: 生产者: 通过SEND命令给某个目的地址(destination)发送消息。 消费者: 通过SUBSCRIBE命令订阅某个目的地址(destination), 当生产者发送消息到目的地址后, 订阅此目的地址的消费者会即时收到消息。 ``` [stomp调用demo](/src/main/resources/static/js/message.js) ## 后端如何构建websocket项目 1、集成springboot 2、Spring Configuration - 直接实现implements WebSocketMessageBrokerConfigure接口来开发 ``` @Configuration @EnableScheduling @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/messages").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/queue/", "/topic/"); registry.setApplicationDestinationPrefixes("/app"); } } ``` - 使用 Spring Session 来支持websocket ``` @Configuration @EnableScheduling @EnableWebSocketMessageBroker // 或者可以直接实现implements WebSocketMessageBrokerConfigure接口来开发 // AbstractSessionWebSocketMessageBrokerConfigurer 基于redis实现了session共享,可以保证服务器宕机session不失效 public class WebSocketConfig extends AbstractSessionWebSocketMessageBrokerConfigurer { // <1> /** * endpoints就是连接点 websocket根据该url来进行webscoket连接. * @param registry */ @Override protected void configureStompEndpoints(StompEndpointRegistry registry) { // <2> // 为了sockjs可以进行连接,并设置可以跨域 registry.addEndpoint("/messages").setAllowedOrigins("*").withSockJS(); // 普通的stomp client进行连接 registry.addEndpoint("/messages"); } /** * * @param registry */ @Override public void configureMessageBroker(MessageBrokerRegistry registry) { // 配置前缀, 有这些前缀的会路由到broker registry.enableSimpleBroker("/queue/", "/topic/") //配置stomp协议里, server返回的心跳 .setHeartbeatValue(new long[]{10000L, 30000L}) //配置发送心跳的scheduler .setTaskScheduler(new DefaultManagedTaskScheduler()); // 配置使用外部broker // registry.enableStompBrokerRelay("/queue/", "/topic/") // .setRelayHost("外部mq的地址") // .setRelayPort("外部mq stomp的端口"); // 配置前缀, 有这些前缀的会被到有@SubscribeMapping与@MessageMapping的业务方法拦截 registry.setApplicationDestinationPrefixes("/app"); } } ``` AbstractSessionWebSocketMessageBrokerConfigurer在幕后做什么? ``` WebSocketConnectHandlerDecoratorFactory作为WebSocketHandlerDecoratorFactory添加到WebSocketTransportRegistration。这确保了一个自定义的包含WebSocketSession的SessionConnectEvent被触发。当Spring会话被终止时,WebSocketSession必须终止任何仍然打开的WebSocket连接。 SessionRepositoryMessageInterceptor作为一个HandshakeInterceptor添加到每个StompWebSocketEndpointRegistration。这可以确保会话被添加到WebSocket属性中,以更新上次访问的时间。 SessionRepositoryMessageInterceptor作为ChannelInterceptor添加到我们的入站ChannelRegistration中。这确保了每次接收到入站消息时,Spring会话的最后一次访问时间都会被更新。 WebSocketRegistryListener被创建为一个Spring bean。这确保了我们拥有所有会话id到相应WebSocket连接的映射。通过维护这个映射,我们可以在Spring会话(HttpSession)终止时关闭所有的WebSocket连接。 ``` 设置session回话失效时间 - src/main/resources/application.properties ``` server.servlet.session.timeout=1m # Session timeout. If a duration suffix is not specified, seconds will be used. ``` ## Spring 封装的 STOMP Spring 官方提供的处理流图 ![websocket 流程图](docs/images/4.png) 当从WebSocket连接接收到消息时,它们被解码到STOMP帧,转换为Spring Message表示,并发送到clientInboundChannel进行进一步处理。例如,以/app开头的destination header的STOMP消息可以路由到带注解的控制器中的@MessageMapping方法,而/topic和/queue消息可以直接路由到消息代理。 处理来自客户端的STOMP消息的@Controller注解可以通过brokerChannel向消息代理发送消息,而代理通过clientOutboundChannel向匹配的订阅者广播消息。相同的控制器也可以对HTTP请求进行相同的响应,因此客户端可以执行HTTP POST,然后@PostMapping方法可以向消息代理发送消息,以便向订阅的客户端广播消息。 一些demo demo网上已经有很多了, 可以依照这些demo来搭建一个spring stomp over websocket应用, 比如: - [简单的聊天应用](https://spring.io/guides/gs/messaging-stomp-websocket) - [简单的股票行情应用](https://github.com/rstoyanchev/spring-websocket-portfolio) ## 参考资料 - [WebSocket Support Prev Part](https://docs.spring.io/spring-framework/docs/4.1.5.RELEASE/spring-framework-reference/html/websocket.html) - [websocket使用(官网教程):websocket usage](https://docs.spring.io/spring-session/docs/current/reference/html5/index.html#websocket) - [websocket官方demo-官方实例项目](https://docs.spring.io/spring-session/docs/current/reference/html5/guides/boot-websocket.html) - [RFC文档-第7页](https://tools.ietf.org/html/rfc6455) - [Websocket HandShake Sec-WebSocket-Accept 生成策略](https://my.oschina.net/shipley/blog/533154) - [WebSocket 是什么原理?为什么可以实现持久连接?](https://www.zhihu.com/question/20215561/answer/40316953) - [stomp over websocket协议原理与实现](https://blog.csdn.net/ttdevs/article/details/62887058) - [SpringBoot 实现 Websocket 通信详解](http://www.mydlq.club/article/86/) - [websocket token-authentication](https://github.com/spring-projects/spring-framework/blob/master/src/docs/asciidoc/web/websocket.adoc#token-authentication) - [WebSocket Security(网络套接字安全)](https://www.cnblogs.com/jrkl/p/13513439.html)