RFC 6455 即 webSockets 协议提供了一种标准化的方式去建立全双工,双方面交流的通道在客户端和服务端甚至单一的TCP连接中进行通信; webSockets 协议其跟HTTP的tcp协议不同,但是其设计目的是通过HTTP协议进行工作,可以使用40或者443端口和重新使用现有的防火墙规则;
GET /spring-websocket-portfolio/portfolio HTTP/1.1 Host: localhost:8080 Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg== Sec-WebSocket-Protocol: v10.stomp, v11.stomp Sec-WebSocket-Version: 13 Origin: http://localhost:8080 复制代码
webSockets 的交互是以HTTP协议开始的,使用 Upgrade
header 转向使用Upgrade连接;如果非200状态成功响应就类似于下面的信息;如果WebSocket server 是运行在nginx是需要配置WebSocket upgrade requests ;如果是运行在云上,需要查阅相关的云是否支持WebSocket ;
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0= Sec-WebSocket-Protocol: v10.stomp 复制代码
STOMP
)和基于HTTP握手请求的 Sec-WebSocket-Protocol
header ; WebSockets可以使web页面具有动态性和交互性。然而,在许多情况下,Ajax和HTTP流或 long polling(轮询)可以提供一个简单有效的解决方案。HTTP流和polling适用于消息不频繁的交互,WebSockets适用于消息较频繁的交互;在 因特网上由于没有Upgrade header 或者 关闭了空闲的长链接,受限于在你有限的代理可能会将WebSockets的交互排除;
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> </parent> <dependencies> <!-- websocket依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <!-- 模板引擎--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies> 复制代码
/** * @Author lsc * @Description <p>websocket配置类 </p> * @Date 2019/11/12 22:27 */ //使用STOMP协议来传输基于消息代理的消息,控制器支持在@Controller类中使用@MessageMapping @EnableWebSocketMessageBroker @Configurable @EnableWebSocket @Component public class WebConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // 注册 Stomp的端点(Endpoint),并且映射指定的url registry.addEndpoint("/websocket") .setAllowedOrigins("*") // 添加允许跨域访问 .withSockJS();// 指定SockJS协议 } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { // 启动广播模式代理,只有符合的的路径才发送消息 registry.enableSimpleBroker("/topic"); } } 复制代码
/** * @Author lsc * @Description <p> 接受客户端消息</p> * @Date 2019/11/12 22:42 */ public class AcceptMessages { private String name; public String getName() { return name; } } 复制代码
/** * @Author lsc * @Description <p>发送消息给客户端 </p> * @Date 2019/11/12 22:42 */ public class SendMessages { private String responseMessage; public String getResponseMessage() { return responseMessage; } public void setResponseMessage(String responseMessage) { this.responseMessage = responseMessage; } } 复制代码
/** * @Author lsc * @Description <p>websockets 之 广播式</p> * @Date 2019/11/12 22:49 */ @Controller public class WebSocketsController { @MessageMapping("/welcome")//类似@RequestMapping,进行客户端请求地址映射 @SendTo("/topic/getResponse")//订阅了@SendTo中的路径进行发送消息 public SendMessages broadcast(AcceptMessages acceptMessages){ System.out.println(acceptMessages.getName()); SendMessages sendMessages = new SendMessages(); sendMessages.setResponseMessage("知识追寻者:"+acceptMessages.getName()); return sendMessages; } } 复制代码
在 resource目录下新建templates目录存放WebSockets.html;在resource目录下新建static目录,继续在其子目录下新建js目录存放sockjs.min.js,stomp.min.js,jquery-3.3.1.min.js;
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"/> <title>springboot广播式WebSocket</title> </head> <body onload="disconnect()"> <noscript><h2 style="color: #ffff0000;">Sorry,not support the WebSocket</h2></noscript> <div> <div> <button id="connect" onclick="connect();">连接</button> <button id="disconnect" disabled="disabled" onclick="disconnect();">断开连接</button> </div> <div id="conversationDiv"> <label>输入你的名字</label> <input type="text" id="name"/> <button id="sendName" onclick="sendName();">发送</button> <p id="response"></p> </div> </div> <script th:src="@{js/sockjs.min.js}"></script> <script th:src="@{js/stomp.min.js}"></script> <script th:src="@{js/jquery-3.3.1.min.js}"></script> <script type="text/javascript"> var stompClient = null; // 设置连接 function setConnected(connected) { document.getElementById("connect").disabled = connected; document.getElementById("disconnect").disabled = !connected; document.getElementById("conversationDiv").style.visibility = connected ? 'visible' : 'hidden'; $("#response").html(); } // 连接 function connect() { // 转向 endpoint 名为websocket var socket = new SockJS('/websocket'); // 使用ssocket的协议 stompClient = Stomp.over(socket); // 连接 stompClient.connect({}, function (frame) { setConnected(true); console.log('Connected:' + frame); // @Sendto 中定义路径 向目标订阅消息 stompClient.subscribe('/topic/getResponse', function (response) { showResponse(JSON.parse(response.body).responseMessage); }) }); } function disconnect() { if (stompClient != null) { stompClient.disconnect(); } setConnected(false); console.log('Disconnected'); } function sendName() { var name = $('#name').val(); // 控制器@MessageMapping中定义向目标发送消息 stompClient.send("/welcome", {}, JSON.stringify({'name': name})); } function showResponse(message) { $("#response").html(message); } </script> </body> </html> 复制代码
当客户端请求地址是localhost:8080/ws,经过springmvc视图转发器至WebSockets.html;
/** * @Author lsc * @Description <p> spingmvc视图映射转发</p> * @Date 2019/11/12 23:35 */ @Configurable @Component public class WebMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { // 配置视图转发 registry.addViewController("/ws").setViewName("/WebSockets"); } } 复制代码