WebSocket 教程
概述
WebSocket 是什麼?
WebSocket 是一種網絡通信協定。RFC6455 定義了它的通信标準。
WebSocket 是 HTML5 開始提供的一種在單個 TCP 連接配接上進行全雙工通訊的協定。
為什麼需要 WebSocket ?
了解計算機網絡協定的人,應該都知道:HTTP 協定是一種無狀态的、無連接配接的、單向的應用層協定。它采用了請求/響應模型。通信請求隻能由用戶端發起,服務端對請求做出應答處理。
這種通信模型有一個弊端:HTTP 協定無法實作伺服器主動向用戶端發起消息。
這種單向請求的特點,注定了如果伺服器有連續的狀态變化,用戶端要獲知就非常麻煩。大多數 Web 應用程式将通過頻繁的異步JavaScript和XML(AJAX)請求實作長輪詢。輪詢的效率低,非常浪費資源(因為必須不停連接配接,或者 HTTP 連接配接始終打開)。
ajax-long-polling.png
是以,工程師們一直在思考,有沒有更好的方法。WebSocket 就是這樣發明的。WebSocket 連接配接允許用戶端和伺服器之間進行全雙工通信,以便任一方都可以通過建立的連接配接将資料推送到另一端。WebSocket 隻需要建立一次連接配接,就可以一直保持連接配接狀态。這相比于輪詢方式的不停建立連接配接顯然效率要大大提高。
websockets-flow.png
WebSocket 如何工作?
Web浏覽器和伺服器都必須實作 WebSockets 協定來建立和維護連接配接。由于 WebSockets 連接配接長期存在,與典型的HTTP連接配接不同,對伺服器有重要的影響。
基于多線程或多程序的伺服器無法适用于 WebSockets,因為它旨在打開連接配接,盡可能快地處理請求,然後關閉連接配接。任何實際的 WebSockets 伺服器端實作都需要一個異步伺服器。
WebSocket 用戶端
在用戶端,沒有必要為 WebSockets 使用 JavaScript 庫。實作 WebSockets 的 Web 浏覽器将通過 WebSockets 對象公開所有必需的用戶端功能(主要指支援 Html5 的浏覽器)。
用戶端 API
以下 API 用于建立 WebSocket 對象。
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 實作有以下三種。
- µWebSockets
- Socket.IO
- WebSocket-Node
Java
Java 的 web 一般都依托于 servlet 容器。
我使用過的 servlet 容器有:Tomcat、Jetty、Resin。其中Tomcat7、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 處理器
擴充
TextWebSocketHandler
或
BinaryWebSocketHandler
,你可以覆寫指定的方法。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 處理器添加到注冊中心。
- 實作
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();
}
}
- 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 沒有直接關系。
熬夜不易,點選請老王喝杯烈酒!!!!!!!