websocket 詳解

WebSocket 詳解教程

 

概述

WebSocket 是什麼?

WebSocket 是一種網絡通訊協議。RFC6455 定義了它的通訊標準。html

WebSocket 是 HTML5 開始提供的一種在單個 TCP 鏈接上進行全雙工通信的協議。java

爲何須要 WebSocket ?

瞭解計算機網絡協議的人,應該都知道:HTTP 協議是一種無狀態的、無鏈接的、單向的應用層協議。它採用了請求/響應模型。通訊請求只能由客戶端發起,服務端對請求作出應答處理。node

這種通訊模型有一個弊端:HTTP 協議沒法實現服務器主動向客戶端發起消息。python

這種單向請求的特色,註定了若是服務器有連續的狀態變化,客戶端要獲知就很是麻煩。大多數 Web 應用程序將經過頻繁的異步JavaScript和XML(AJAX)請求實現長輪詢。輪詢的效率低,很是浪費資源(由於必須不停鏈接,或者 HTTP 鏈接始終打開)。nginx

ajax-long-polling.png

所以,工程師們一直在思考,有沒有更好的方法。WebSocket 就是這樣發明的。WebSocket 鏈接容許客戶端和服務器之間進行全雙工通訊,以便任一方均可以經過創建的鏈接將數據推送到另外一端。WebSocket 只須要創建一次鏈接,就能夠一直保持鏈接狀態。這相比於輪詢方式的不停創建鏈接顯然效率要大大提升。git

websockets-flow.png

WebSocket 如何工做?

Web瀏覽器和服務器都必須實現 WebSockets 協議來創建和維護鏈接。因爲 WebSockets 鏈接長期存在,與典型的HTTP鏈接不一樣,對服務器有重要的影響。github

基於多線程或多進程的服務器沒法適用於 WebSockets,由於它旨在打開鏈接,儘量快地處理請求,而後關閉鏈接。任何實際的 WebSockets 服務器端實現都須要一個異步服務器。web

WebSocket 客戶端

在客戶端,沒有必要爲 WebSockets 使用 JavaScript 庫。實現 WebSockets 的 Web 瀏覽器將經過 WebSockets 對象公開全部必需的客戶端功能(主要指支持 Html5 的瀏覽器)。ajax

客戶端 API

如下 API 用於建立 WebSocket 對象。spring

var Socket = new WebSocket(url, [protocol] );

以上代碼中的第一個參數 url, 指定鏈接的 URL。第二個參數 protocol 是可選的,指定了可接受的子協議。

WebSocket 屬性

如下是 WebSocket 對象的屬性。假定咱們使用了以上代碼建立了 Socket 對象:

屬性 描述
Socket.readyState 只讀屬性 readyState 表示鏈接狀態,能夠是如下值:0 - 表示鏈接還沒有創建。1 - 表示鏈接已創建,能夠進行通訊。2 - 表示鏈接正在進行關閉。3 - 表示鏈接已經關閉或者鏈接不能打開。
Socket.bufferedAmount 只讀屬性 bufferedAmount 已被 send() 放入正在隊列中等待傳輸,可是尚未發出的 UTF-8 文本字節數。

WebSocket 事件

如下是 WebSocket 對象的相關事件。假定咱們使用了以上代碼建立了 Socket 對象:

事件 事件處理程序 描述
open Socket.onopen 鏈接創建時觸發
message Socket.onmessage 客戶端接收服務端數據時觸發
error Socket.onerror 通訊發生錯誤時觸發
close Socket.onclose 鏈接關閉時觸發

WebSocket 方法

如下是 WebSocket 對象的相關方法。假定咱們使用了以上代碼建立了 Socket 對象:

方法 描述
Socket.send() 使用鏈接發送數據
Socket.close() 關閉鏈接

示例

// 初始化一個 WebSocket 對象
var ws = new WebSocket("ws://localhost:9998/echo");

// 創建 web socket 鏈接成功觸發事件
ws.onopen = function () {
  // 使用 send() 方法發送數據
  ws.send("發送數據");
  alert("數據發送中...");
};

// 接收服務端數據時觸發事件
ws.onmessage = function (evt) {
  var received_msg = evt.data;
  alert("數據已接收...");
};

// 斷開 web socket 鏈接成功觸發事件
ws.onclose = function () {
  alert("鏈接已關閉...");
};

WebSocket 服務端

WebSocket 在服務端的實現很是豐富。Node.js、Java、C++、Python 等多種語言都有本身的解決方案。

如下,介紹我在學習 WebSocket 過程當中接觸過的 WebSocket 服務端解決方案。

Node.js

經常使用的 Node 實現有如下三種。

Java

Java 的 web 通常都依託於 servlet 容器。

我使用過的 servlet 容器有:Tomcat、Jetty、Resin。其中Tomcat七、Jetty7及以上版本均開始支持 WebSocket(推薦較新的版本,由於隨着版本的更迭,對 WebSocket 的支持可能有變動)。

此外,Spring 框架對 WebSocket 也提供了支持。

雖然,以上應用對於 WebSocket 都有各自的實現。可是,它們都遵循RFC6455 的通訊標準,而且 Java API 統一遵循 JSR 356 - JavaTM API for WebSocket 規範。因此,在實際編碼中,API 差別不大。

Spring

Spring 對於 WebSocket 的支持基於下面的 jar 包:

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-websocket</artifactId>
  <version>${spring.version}</version>
</dependency>

在 Spring 實現 WebSocket 服務器大概分爲如下幾步:

建立 WebSocket 處理器

擴展 TextWebSocketHandlerBinaryWebSocketHandler ,你能夠覆寫指定的方法。Spring 在收到 WebSocket 事件時,會自動調用事件對應的方法。

import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.TextMessage;

public class MyHandler extends TextWebSocketHandler {

    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) {
        // ...
    }

}

WebSocketHandler 源碼以下,這意味着你的處理器大概能夠處理哪些 WebSocket 事件:

public interface WebSocketHandler {

   /**
    * 創建鏈接後觸發的回調
    */
   void afterConnectionEstablished(WebSocketSession session) throws Exception;

   /**
    * 收到消息時觸發的回調
    */
   void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception;

   /**
    * 傳輸消息出錯時觸發的回調
    */
   void handleTransportError(WebSocketSession session, Throwable exception) throws Exception;

   /**
    * 斷開鏈接後觸發的回調
    */
   void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception;

   /**
    * 是否處理分片消息
    */
   boolean supportsPartialMessages();

}

配置 WebSocket

配置有兩種方式:註解和 xml 。其做用就是將 WebSocket 處理器添加到註冊中心。

  1. 實現 WebSocketConfigurer
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/myHandler");
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }

}
  1. xml 方式
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers>
        <websocket:mapping path="/myHandler" handler="myHandler"/>
    </websocket:handlers>

    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>

</beans>

更多配置細節能夠參考:Spring WebSocket 文檔

javax.websocket

若是不想使用 Spring 框架的 WebSocket API,你也能夠選擇基本的 javax.websocket。

首先,須要引入 API jar 包。

<!-- To write basic javax.websocket against -->
<dependency>
  <groupId>javax.websocket</groupId>
  <artifactId>javax.websocket-api</artifactId>
  <version>1.0</version>
</dependency>

若是使用嵌入式 jetty,你還須要引入它的實現包:

<!-- To run javax.websocket in embedded server -->
<dependency>
  <groupId>org.eclipse.jetty.websocket</groupId>
  <artifactId>javax-websocket-server-impl</artifactId>
  <version>${jetty-version}</version>
</dependency>
<!-- To run javax.websocket client -->
<dependency>
  <groupId>org.eclipse.jetty.websocket</groupId>
  <artifactId>javax-websocket-client-impl</artifactId>
  <version>${jetty-version}</version>
</dependency>

@ServerEndpoint

這個註解用來標記一個類是 WebSocket 的處理器。

而後,你能夠在這個類中使用下面的註解來代表所修飾的方法是觸發事件的回調

// 收到消息觸發事件
@OnMessage
public void onMessage(String message, Session session) throws IOException, InterruptedException {
    ...
}

// 打開鏈接觸發事件
@OnOpen
public void onOpen(Session session, EndpointConfig config, @PathParam("id") String id) {
    ...
}

// 關閉鏈接觸發事件
@OnClose
public void onClose(Session session, CloseReason closeReason) {
    ...
}

// 傳輸消息錯誤觸發事件
@OnError
public void onError(Throwable error) {
    ...
}

ServerEndpointConfig.Configurator

編寫完處理器,你須要擴展 ServerEndpointConfig.Configurator 類完成配置:

public class WebSocketServerConfigurator extends ServerEndpointConfig.Configurator {
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
    }
}

而後就沒有而後了,就是這麼簡單。

WebSocket 代理

若是把 WebSocket 的通訊當作是電話鏈接,Nginx 的角色則像是電話接線員,負責將發起電話鏈接的電話轉接到指定的客服。

Nginx 從 1.3 版開始正式支持 WebSocket 代理。若是你的 web 應用使用了代理服務器 Nginx,那麼你還須要爲 Nginx 作一些配置,使得它開啓 WebSocket 代理功能。

如下爲參考配置:

server {
  # this section is specific to the WebSockets proxying
  location /socket.io {
    proxy_pass http://app_server_wsgiapp/socket.io;
    proxy_redirect off;

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 600;
  }
}

更多配置細節能夠參考:Nginx 官方的 websocket 文檔

FAQ

HTTP 和 WebSocket 有什麼關係?

Websocket 實際上是一個新協議,跟 HTTP 協議基本沒有關係,只是爲了兼容現有瀏覽器的握手規範而已,也就是說它是 HTTP 協議上的一種補充。

Html 和 HTTP 有什麼關係?

Html 是超文本標記語言,是一種用於建立網頁的標準標記語言。它是一種技術標準。Html5 是它的最新版本。

Http 是一種網絡通訊協議。其自己和 Html 沒有直接關係。

完整示例

若是須要完整示例代碼,能夠參考個人 Github 代碼:

資料

相關文章
相關標籤/搜索