websocket是html5提出的一種基於tcp的協議(可參考rfc6455---websocket協議)。是先經過http/https協議發起一條特殊的http請求進行握手後建立一個用於數據傳輸的tcp通道,此後服務端與客戶端經過此TCP鏈接進行實時通訊。它跟HTTP協議基本沒有關係,只是爲了兼容現有瀏覽器的握手規範而已。html
瞭解計算機網絡的人,應該都知道,http是一種無狀態的、無鏈接的,單向的應用層協議,默認端口爲80,而且每次請求都要開啓一條鏈接,發送請求--》接收數據--》關閉鏈接這樣一個過程,請求只能由客戶端發起,而且HTTP request 的header是很是長的,裏面包含的數據可能只是一個很小的值,這樣會佔用不少的帶寬和服務器資源。html5
這樣還有一個弊端:http協議沒法實現服務器端主動向客戶端發起消息。java
這種單向請求的特色,註定了若是服務器有連續的狀態變化,客戶端要獲知就很是麻煩。之前web server實現推送技術或者即時通信,用的都是輪詢(polling),而輪詢能夠經過ajax輪詢、long poll(長輪詢)等方式實現,這些技術雖然可達到全雙工通訊,可是輪詢的效率低,很是浪費資源(由於必須不停鏈接,或者 HTTP 鏈接始終打開)。python
websocket就是在這樣的環境下誕生的,它只須要一次握手,就能實現客戶端和服務器之間進行全雙工通訊,任一方均可以經過創建的鏈接將數據推送到另外一端。nginx
傳統輪詢方式:git
這兩種方式雖然能實現雙工通訊,都是很是消耗服務器資源的。github
websocket的優勢:web
TCP
協議之上,服務器端的實現比較容易。HTTP
協議有着良好的兼容性。默認端口也是80
和443
,而且握手階段採用HTTP
協議;ws
(若是加密,則爲wss
),服務器網址就是 URL
。websocket的缺點:ajax
如下 API 用於建立 WebSocket 對象。 spring
var Socket = new WebSocket(url, [protocol] );
以上代碼中的第一個參數 url, 指定鏈接的 URL。第二個參數 protocol 是可選的,指定了可接受的子協議。
如下是 WebSocket 對象的屬性。假定咱們使用了以上代碼建立了 Socket 對象:
屬性 | 描述 |
---|---|
Socket.readyState | 只讀屬性 readyState 表示鏈接狀態,能夠是如下值:0 - 表示鏈接還沒有創建。1 - 表示鏈接已創建,能夠進行通訊。2 - 表示鏈接正在進行關閉。3 - 表示鏈接已經關閉或者鏈接不能打開。 |
Socket.bufferedAmount | 只讀屬性 bufferedAmount 已被 send() 放入正在隊列中等待傳輸,可是尚未發出的 UTF-8 文本字節數。 |
如下是 WebSocket 對象的相關事件。假定咱們使用了以上代碼建立了 Socket 對象:
事件 | 事件處理程序 | 描述 |
---|---|---|
open | Socket.onopen | 鏈接創建時觸發 |
message | Socket.onmessage | 客戶端接收服務端數據時觸發 |
error | Socket.onerror | 通訊發生錯誤時觸發 |
close | Socket.onclose | 鏈接關閉時觸發 |
如下是 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 在服務端的實現很是豐富。Node.js、Java、C++、Python 等多種語言都有本身的解決方案。
如下,介紹我在學習 WebSocket 過程當中接觸過的 WebSocket 服務端解決方案。
經常使用的 Node 實現有如下三種。
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 對於 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(); } }
<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 文檔
若是不想使用 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 的通訊當作是電話鏈接,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 文檔
Websocket 實際上是一個新協議,跟 HTTP 協議基本沒有關係,只是爲了兼容現有瀏覽器的握手規範而已,也就是說它是 HTTP 協議上的一種補充。
Html 是超文本標記語言,是一種用於建立網頁的標準標記語言。它是一種技術標準。Html5 是它的最新版本。
Http 是一種網絡通訊協議。其自己和 Html 沒有直接關係。
若是須要完整示例代碼,能夠參考個人 Github 代碼:
spring-websocket 和 jetty 9.3 版本彷佛存在兼容性問題,Tomcat則木有問題。
我嘗試了好幾回,沒有找到解決方案,只好使用 Jetty 官方的嵌入式示例在 Jetty 中使用 WebSocket 。
知乎高票答案——WebSocket是什麼原理 by Ovear
對 WebSocket 原理的闡述簡單易懂。
WebSocket 教程 by ruanyf
阮一峯大神的科普一如既往的淺顯易懂。
WebSockets by fullstackpython
本文章感謝靜默虛空做者文章支持,文章地址: