目錄javascript
從HTML5技術流行至今,WebSocket已經有很是普遍的應用:css
...html
這些場景,都須要服務器能主動實時的給瀏覽器或客戶端推送消息,注意關鍵詞是主動,還有實時!
而在HTML5一統江湖以前,因爲HTTP在推送場景下的"薄弱",咱們須要藉助一些複雜或者非標準的手段來實現。前端
這些方式包括有:java
在這種方案下,瀏覽器須要不斷的向服務器發出請求,問題是比較明顯的,包括:jquery
Comet 效率提高了很多,它解決了Ajax輪詢的部分問題,利用HTTP長鏈接的特性儘量的避免了鏈接、帶寬資源的浪費等等,因而在很長一段時間 Comet 成爲了Web推送技術的主流。
But ,.. Comet 的實現技術比較複雜,不一樣框架下的實現方式差別很大,在靈活性、性能上也有些欠缺。
關於服務端Comet的技術能夠參考下面這篇經典文章:
https://www.ibm.com/developerworks/cn/web/wa-lo-comet/git
WebSocket 出現的目的沒有別的,就是幹掉前面的東西,Both!
最開始WebSocket 協議由 RFC6455 定義,其API標準包含於HTML5 範疇之中。
目前各大主流瀏覽器已經能徹底支持該技術。而後能夠看看下面這個圖:github
如上圖,WebSocket 協議中, 瀏覽器和服務器只須要完成一次握手,二者之間就直接能夠建立持久性的鏈接,並進行雙向數據傳輸。
那麼相比以往的方式,這種方案更加節省資源了,它的實時性、靈活性都要強大很多。
固然,有HTML5標準給它站臺,後臺槓槓的~web
那麼一個 WebSocket 的請求響應長成怎麼樣呢?
看下面這個圖:spring
一開始我一直認爲 Stomp是暴風雨(誤看爲 Storm),而後以爲說這個技術挺犀利的。
而後在看了 Stomp 的協議介紹後發現,它是如此的簡單..
Stomp 的 全稱叫 Simple Text Orientated Messaging Protocol,就是一個簡單的文本定向消息協議,
除了設計爲簡單易用以外,它的支持者也很是多。就好比目前主流的消息隊列服務器如RabbitMQ、ActiveMQ都支持Stomp 協議。
開源地址:
http://stomp.github.io/
Stomp 定義了一些簡單的指令,以下:
命令 | 說明 |
---|---|
CONNECT | 創建鏈接 |
SEND | 發送消息 |
SUBSCRIBE | 訂閱主題 |
UNSUBSCRIBE | 取消訂閱 |
BEGIN | 開啓事務 |
COMMIT | 提交事務 |
ABORT | 回滾事務 |
ACK | 確認消費 |
NACK | 消息丟棄 |
DISCONNECT | 斷開鏈接 |
一個簡單的STOMP消息大體以下:
CONNECT accept-version:1.1,1.0 heart-beat:10000,10000\n\n\u0000 SEND destination:/app/message\ncontent-length:6 發送內容\u0000
好的,你如今應該瞭解 Stomp是個什麼了,那麼爲何要介紹這個?
WebSocket 爲咱們提供了Web 雙向通訊的通道,但對於消息的交互協議還須要咱們來本身實現(WebSocket 果真不夠意思)
藉助Stomp 協議,能夠很方便的實現一種"訂閱-發佈"的通用機制,這個就是很是具備競爭力的一個特性了。
在介紹完WebSocket 以後,接下來幹什麼呢?
可能你看完前面的東西會以爲 WebSocket 是如此之強大,以致於不少場景都應該使用這個技術來實現。
那麼如何作? 在此前我所介紹的 SpringBoot 也是如此之強大,那麼能不能經過SpringBoot 輕鬆整合WebSocket 呢?這固然能夠!
思索了好久,我決定作一個最簡單的應用展現: 尬聊!
爲何是"尬聊」,而不是聊天室...
那麼,下面開始講這個案例,在該樣例中會包含一個Controller類、一個HTML頁面以及一個JS腳本。
步驟以下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> <version>${springboot.version}</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </exclusion> </exclusions> </dependency> <!--websocket--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <version>${springboot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <version>${springboot.version}</version> <optional>true</optional> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator-core</artifactId> <version>0.32</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>sockjs-client</artifactId> <version>1.0.2</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>stomp-websocket</artifactId> <version>2.3.3</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>2.1.4</version> </dependency> <dependency> <groupId>org.foo.springboot</groupId> <artifactId>base</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- jackson version --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.8.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.8.3</version> </dependency>
添加spring-boot-starter-websocket 會自動引入spring-websocket的依賴,然後者就實現了WebSocket 操做的高級封裝。
還有一個好消息,就是spring-websocket 也默認支持了 Stomp協議(看吧,Stomp支持者太多了)。
而除此以外,還內置了一個叫 SocketJS 的東西。
SocketJS是一個流行的JS庫,主要是在WebSocket之上封裝了一層API,用於支持瀏覽器不兼容WebSocket的狀況。
其項目地址:
https://github.com/sockjs/sockjs-client
其餘組件的說明
參考下面的代碼,添加一個JavaConfig風格的配置類:
WebSocketConfig.java
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { private static final Logger logger = LoggerFactory.getLogger(WebSocketConfig.class); @Override public void configureMessageBroker(MessageBrokerRegistry config) { //設置訂閱通道(客戶端可訂閱) config.enableSimpleBroker("/topic"); //接收APP(客戶端)消息的路由前綴,可經過@MessageMapping 映射到方法 config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { //websocket 鏈接端點 registry.addEndpoint("/backend").withSockJS(); } @Override public void configureWebSocketTransport(final WebSocketTransportRegistration registration) { //配置攔截器 registration.addDecoratorFactory(new WebSocketHandlerDecoratorFactory() { @Override public WebSocketHandler decorate(final WebSocketHandler handler) { return new WebSocketHandlerDecorator(handler) { @Override public void afterConnectionEstablished(final WebSocketSession session) throws Exception { String username = session.getPrincipal() != null? session.getPrincipal().getName(): "GUEST"; logger.info("{} connect.", username); super.afterConnectionEstablished(session); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { String username = session.getPrincipal() != null? session.getPrincipal().getName(): "GUEST"; logger.info("{} disconnect.", username); super.afterConnectionClosed(session, closeStatus); } }; } }); super.configureWebSocketTransport(registration); } }
在WebSocketConfig的配置中,有兩點須要關注:
控制層除了支持頁面的渲染,還須要對WebSocket消息進行處理,實現以下:
ConsoleController
@Controller public class ConsoleController { //輸出數據頻道 public static final String CHANNEL_CONSOLE = "/topic/console"; @Autowired private SimpMessagingTemplate template; /** * 控制檯頁面 * * @return */ @GetMapping("/console") public String console() { return "console"; } /** * 接收WebSocket消息方法 * @param message */ @MessageMapping("/message") public void onMessage(String message) { template.convertAndSend(CHANNEL_CONSOLE, "我收到了你的消息:" + message); } }
先作一個HTML頁面,編輯templates/console.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"></meta> <title>Web控制檯</title> <script th:src="@{/webjars/sockjs-client/sockjs.min.js}"></script> <script th:src="@{/webjars/stomp-websocket/stomp.min.js}"></script> <script th:src="@{/webjars/jquery/jquery.min.js}"></script> <script type="text/javascript" th:src="@{/static/console.js}"></script> <style type="text/css"> body { font-family: "Microsoft YaHei" ;} .span-tv{padding-right:12px} #console p {padding: 0px; margin: 0px;} </style> </head> <body> <div style="background-color:#AAA; padding: 5px; border-bottom: 1px solid #333"> <input type="text" id="word" style="width:100px"></input> <button onclick="sendMessage()">發送消息</button> <button onclick="reconnect()">從新鏈接</button> <button onclick="clearConsole()">清空內容</button> </div> <div id="console" style="padding:5px; font-size:10px"></div> </body> </html>
而後是實現 JS 腳本,編輯public/static/console.js
$(document).ready(function(){ //首次打開頁面自動鏈接 connect(); }) //執行鏈接 function connect() { //接入端點/backend var socket = new SockJS('/backend'); window.stompClient = Stomp.over(socket); window.stompClient.connect({}, function (frame) { log('Connected: ' + frame); //訂閱服務端輸出的 Topic stompClient.subscribe('/topic/console', function (message) { log("[服務器說]:" + message.body); }); }); } //斷開鏈接 function disconnect() { if (stompClient !== null) { stompClient.disconnect(); } log("Disconnected"); } //從新鏈接 function reconnect(){ clearConsole(); disconnect(); connect(); } //發送消息 function sendMessage(){ var content = $("#word").val(); if(!content){ alert("請輸入消息!") return; } //嚮應用Topic發送消息 stompClient.send("/app/message", {}, content); log("[你說]:" + content); } //記錄控制檯消息 function log(message){ $("<p></p>").text(message).appendTo($("#console")); } //清空控制檯 function clearConsole(){ $("#console").empty(); }
這樣,Web控制檯已經制做好了,運行主程序後,打開地址
http://localhost:8080/console
進行體驗,以下:
好了,這個案例的確很尷尬..
可是我認爲,在這上面作一作改造,應該能夠實現一個諸如"美女聊天室" 的功能的,或者,你能夠動手試試。
https://spring.io/guides/gs/messaging-stomp-websocket/
https://blog.coding.net/blog/spring-static-resource-process
https://zh.wikipedia.org/wiki/WebSocket
https://halfrost.com/websocket/
歡迎繼續關注"美碼師的補習系列-springboot篇" ,期待更多精彩內容^-^