websocket原理和應用入門

程式設計師屆的吳彥祖發表於2019-04-19

原理

WebSocket是一種在單個TCP連線上進行全雙工通訊的協議。

WebSocket 協議在2008年誕生,2011年成為國際標準。所有瀏覽器都已經支援了。

在WebSocket API中,瀏覽器和伺服器只需要完成一次握手,兩者之間就直接可以建立永續性的連線,並進行雙向資料傳輸。

http協議中有keep-alive,只是把多個HTTP請求合併到一個tcp連線裡進行傳輸, Websocket 是一個新協議,跟HTTP協議基本沒有關係,只是為了相容現有瀏覽器的握手規範,也就是說它是HTTP協議上的一種補充。

websocket原理和應用入門



Websocket協議


HTTP協議中,HTTP的生命週期通過 Request 來界定,一個 Request對應 一個 Response,客戶端發起一個Request,在收到Response之後,一次HTTP請求生命週期結束。而且必須客戶端先傳送一個Request,服務端才能給客戶端傳送Response,伺服器端屬於被動的,不能主動發起。WebSocket的基於長連線,可以雙向通訊,生命週期如下:

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的場景有:

  1. 瀏覽器不支援
  2. Web容器不支援,如tomcat7以前的版本不支援WebSocket
  3. 防火牆不允許
  4. 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的一個樣例

  1. 引入依賴
    <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId></dependency>複製程式碼
  2. 新增配置

    @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");


  3. 提供介面
     @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前戳,前面也已經講述。

  4. 傳送訊息
     SimpMessagingTemplate.convertAndSendToUser(String user, String destination, Object payload)複製程式碼
    SimpMessagingTemplate.convertAndSend(D destination, Object payload)複製程式碼


相關文章