原理
WebSocket是一種在單個TCP連線上進行全雙工通訊的協議。
WebSocket 協議在2008年誕生,2011年成為國際標準。所有瀏覽器都已經支援了。
在WebSocket API中,瀏覽器和伺服器只需要完成一次握手,兩者之間就直接可以建立永續性的連線,並進行雙向資料傳輸。
http協議中有keep-alive,只是把多個HTTP請求合併到一個tcp連線裡進行傳輸, Websocket
是一個新協議,跟HTTP協議基本沒有關係,只是為了相容現有瀏覽器的握手規範,也就是說它是HTTP協議上的一種補充。
Websocket協議
HTTP協議中,HTTP的生命週期通過 Request
來界定,一個 Request對應
一個 Response,客戶端發起一個Request,在收到Response之後,一次HTTP請求生命週期結束。而且必須客戶端先傳送一個Request,服務端才能給客戶端傳送Response,伺服器端屬於被動的,不能主動發起。WebSocket的基於長連線,可以雙向通訊,生命週期如下:
有如下特點:
(1)建立在 TCP 協議之上,伺服器端的實現比較容易。
(2)與 HTTP 協議有著良好的相容性。預設埠也是80和443,並且握手階段採用 HTTP 協議,因此握手時不容易遮蔽,能通過各種 HTTP 代理伺服器。
(3)資料格式比較輕量,效能開銷小,通訊高效。
(4)可以傳送文字,也可以傳送二進位制資料。
(5)沒有同源限制,客戶端可以與任意伺服器通訊。
(6)協議識別符號是ws
(如果加密,則為wss
),伺服器網址就是 URL。
WebSocket借用HTTP協議來完成首次握手,隨後使用WebSocket進行通訊。
HTTP握手請求如下:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: example.com
Upgrade: websocket Connection: Upgrade 表示協議升級為websocket協議。
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== 客戶端也就是瀏覽器或者其他終端隨機生成一組16位的隨機base64編碼的串
Sec-WebSocket-Protocol: chat, superchat 使用協議
Sec-WebSocket-Version: 13 當前使用協議的版本號
握手返回如下:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
狀態碼和描述 101 Switching Protocols表示協議切換成功
Upgrade: websocket Connection: Upgrade 表示協議升級成功為websocket 複製程式碼
Sec-WebSocket-Accept
這個則是經過伺服器確認,並且加密過後的 Sec-WebSocket-Key
Sec-WebSocket-Protocol
則是表示最終使用的協議
前端程式碼:
var ws = new WebSocket("wss://echo.websocket.org");
ws.onopen = function(evt) {
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};
ws.onmessage = function(evt) {
console.log( "Received Message: " + evt.data);
ws.close();
};
ws.onclose = function(evt) {
console.log("Connection closed.");
};
不支援WebSocket
不支援WebSocket的場景有:
- 瀏覽器不支援
- Web容器不支援,如tomcat7以前的版本不支援WebSocket
- 防火牆不允許
- Nginx沒有開啟WebSocket支援
基於 SockJS 的 WebSocket
在不支援WebSocket的情況下,也可以很簡單地實現WebSocket的功能的,方法就是使用 SockJS。它會優先選擇WebSocket進行連線,但是當伺服器或客戶端不支援WebSocket時,會自動在 XHR流、XDR流、iFrame事件源、iFrame HTML檔案、XHR輪詢、XDR輪詢、iFrame XHR輪詢、JSONP輪詢 這幾個方案中擇優進行連線。
服務端
在啟動WebSocket的配置中,你需要做的所有事情就是加上 withSockJS()
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
// withSockJS 宣告啟用支援 sockJS
webSocketHandlerRegistry.addHandler(marcoHandler(), "/echo").withSockJS();
客戶端
在客戶端需要引入SockJS庫,然後把 new WebSocket(url); 替換成 new SockJS(url);
SockJS類和WebSocket類是相容的,所以可以直接替換
<script type="text/javascript" src="/resources/js/sockjs-1.0.0.min.js"></script>
var sock = new SockJS(url);
應用
以下是一個基於spring、sockjs的一個樣例
- 引入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId></dependency>複製程式碼
- 新增配置
@Configuration@EnableWebSocketMessageBrokerpublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) {複製程式碼
registry .addEndpoint("/websocket/tracker") .setAllowedOrigins("*") .withSockJS();複製程式碼
} @Override public void configureMessageBroker(MessageBrokerRegistry config) {複製程式碼
config.enableSimpleBroker("/topic", "/user"); config.setUserDestinationPrefix("/user"); config.setApplicationDestinationPrefixes("/app");複製程式碼
}} 複製程式碼
1)@EnableWebSocketMessageBroker註解用於開啟使用STOMP協議來傳輸基於代理(MessageBroker)的訊息,這時候控制器(controller)開始支援@MessageMapping,就像是使用@requestMapping一樣。
2).addEndpoint("/websocket/tracker") 註冊一個 Stomp 端點,客戶端連線服務端是使用;.setAllowedOrigins("*")表示允許跨域;.withSockJS()表示指定使用SockJS協議。SockJs是一個WebSocket的通訊js庫,Spring對這個js庫進行了後臺的自動支援,如果使用它不需要進行過多配置。
3)config.enableSimpleBroker("/topic", "/user") 啟用一個簡單的message broker並配置一個或多個字首來過濾針對代理的目的地,客戶端訂閱這個地址,服務端可以向改地址傳送訊息;
config.setApplicationDestinationPrefixes("/app");
- 提供介面
@MessageMapping("/hello") @SendTo("/topic/greetings") public Greeting greeting(HelloMessage message) throws Exception { System.out.println("收到:" + message.toString() + "訊息"); return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!"); }複製程式碼
1)@MessageMapping用於設定URL對映地址,瀏覽器向伺服器發起請求,需要通過該地址,客戶端傳送訊息時,需要訪問/app/hello。
2)@SendTo("/topic/greetings") 設定目的地,伺服器想客戶端傳送訊息,這裡的目的地是站在服務端的角度對客戶端而言。客戶端也需要設定相同的地址,而且必須使用/topic前戳,前面也已經講述。
- 傳送訊息
SimpMessagingTemplate.convertAndSendToUser(String user, String destination, Object payload)複製程式碼
SimpMessagingTemplate.convertAndSend(D destination, Object payload)複製程式碼