WebSocket协议入门介绍
文章目錄
- WebSocket協議是什么
- WebSocket是應用層協議
- WebSocket與Http的區別
- 為什么要使用WebSocket
- 如何使用WebSocket
- 客戶端API
- 在客戶端使用WebSocket
- 在服務端使用WebSocket
- 反向代理對WebSocket的支持
WebSocket協議是什么
WebSocket是應用層協議
WebSocket是基于TCP的應用層協議,用于在C/S架構的應用中實現雙向通信,關于WebSocket協議的詳細規范和定義參見rfc6455。
需要特別注意的是:雖然WebSocket協議在建立連接時會使用HTTP協議,但這并意味著WebSocket協議是基于HTTP協議實現的。
WebSocket與Http的區別
實際上,WebSocket協議與Http協議有著本質的區別:
1.通信方式不同
WebSocket是雙向通信模式,客戶端與服務器之間只有在握手階段是使用HTTP協議的“請求-響應”模式交互,而一旦連接建立之后的通信則使用雙向模式交互,不論是客戶端還是服務端都可以隨時將數據發送給對方;而HTTP協議則至始至終都采用“請求-響應”模式進行通信。也正因為如此,HTTP協議的通信效率沒有WebSocket高。
2.協議格式不同
WebSocket與HTTP的協議格式是完全不同的,具體來講:
(1)HTTP協議(參見:rfc2616)比較臃腫,而WebSocket協議比較輕量。
(2)對于HTTP協議來講,一個數據包就是一條完整的消息;而WebSocket客戶端與服務端通信的最小單位是幀(frame),由1個或多個幀組成一條完整的消息(message)。即:發送端將消息切割成多個幀,并發送給服務端;服務端接收消息幀,并將關聯的幀重新組裝成完整的消息。
WebSocket協議格式:
HTTP請求消息格式:
Request-LineCRLF general-headerCRLF request-headerCRLF entity-headerCRLF CRLF [ message-body ]HTTP響應消息格式:
Status-LineCRLF general-headerCRLF response-headerCRLF entity-headerCRLF CRLF [ message-body ]雖然WebSocket和HTTP是不同應用協議,但rfc6455規定:“WebSocket設計為通過80和443端口工作,以及支持HTTP代理和中介”,從而使其與HTTP協議兼容。為了實現兼容性,WebSocket握手時使用HTTP Upgrade頭從HTTP協議更改為WebSocket協議,參考:WebSocket維基百科 。
為什么要使用WebSocket
隨著Web應用的發展,特別是動態網頁的普及,越來越多的場景需要實現數據動態刷新。
在早期的時候,實現數據刷新的方式通常有如下3種:
1.客戶端定時查詢
客戶端定時查詢(如:每隔10秒鐘查詢一次)是最原始也是最簡單的實現數據刷新的方法,服務端不用做任何改動,只需要在客戶端添加一個定時器即可。但是這種方式的缺點也很明顯:大量的定時請求都是無效的,因為服務端的數據并沒有更新,相應地也導致了大量的帶寬浪費。
2.長輪訓機制
長輪訓機制是對客戶端定時查詢的一種改進,即:客戶端依舊保持定時發送請求給服務端,但是服務端并不立即響應,而是等到真正有數據更新的時候才發送給客戶端。實際上,并不是當沒有數據更新時服務端就永遠都不響應客戶端,而是需要在等待一個超時時間之后結束該次長輪訓請求。相對于客戶端定時查詢方式而言,當數據更新頻率不確定時長輪訓機制能夠很明顯地減少請求數。但是,在數據更新比較頻繁的場景下,長輪訓方式的優勢就沒那么明顯了。
在Web開發中使用得最為普遍的長輪訓實現方案為Comet(Comet (web技術)),Tomcat和Jetty都有對應的實現支持,詳見:WhatIsComet,Why Asynchronous Servlets。
3.HTTP Streaming
不論是長輪訓機制還是傳統的客戶端定時查詢方式,都需要客戶端不斷地發送請求以獲取數據更新,而HTTP Streaming則試圖改變這種方式,其實現機制為:客戶端發送獲取數據更新請求到服務端時,服務端將保持該請求的響應數據流一直打開,只要有數據更新就實時地發送給客戶端。
雖然這個設想是非常美好的,但這帶來了新的問題:
(1)HTTP Streaming的實現機制違背了HTTP協議本身的語義,使得客戶端與服務端不再是“請求-響應”的交互方式,而是直接在二者建立起了一個單向的“通信管道”。
(2)在HTTP Streaming模式下,服務端只要得到數據更新就發送給客戶端,那么就需要客戶端與服務端協商如何區分每一個更新數據包的開始和結尾,否則就可能出現解析數據錯誤的情況。
(3)另外,處于客戶端與服務端的網絡中介(如:代理)可能會緩存響應數據流,這可能會導致客戶端無法真正獲取到服務端的更新數據,這實際上與HTTP Streaming的本意是相違背的。
鑒于上述原因,在實際應用中HTTP Streaming并沒有真正流行起來,反之使用得最多的是長輪訓機制。
顯然,上述幾種實現數據動態刷新的方式都是基于HTTP協議實現的,或多或少地存在這樣那樣的問題和缺陷;而WebSocket是一個全新的應用層協議,專門用于Web應用中需要實現動態刷新的場景。
相比起HTTP協議,WebSocket具備如下特點:
如何使用WebSocket
客戶端API
在Web應用的網頁中使用WebSocket,WebSocket對象提供了用于創建和管理WebSocket連接,以及可以通過該連接發送和接收數據的API。
1.構造函數
可以使用WebSocket類的構造函數(WebSocket(url[, protocols]))實例化一個對象,如:
var url = "ws://host:port/endpoint"; var ws = new WebSocket(url);執行上述語句之后,瀏覽器將與服務端建立一個WebSocket連接,同時返回一個WebSocket實例對象ws。
2.對象屬性
WebSocket實例對象具備如下屬性:
- WebSocket.binaryType: 返回websocket連接所傳輸二進制數據的類型。
- WebSocket.bufferedAmount:只讀屬性,用于返回已經被send()方法放入隊列中但還沒有被發送到網絡中的數據的字節數。一旦隊列中的所有數據被發送至網絡,則該屬性值將被重置為0。但是,若在發送過程中連接被關閉,則屬性值不會重置為0。如果你不斷地調用send(),則該屬性值會持續增長。
- WebSocket.extensions:只讀屬性,返回服務器已選擇的擴展值。目前,鏈接可以協定的擴展值只有空字符串或者一個擴展列表。
- WebSocket.protocol:只讀屬性,用于返回服務器端選中的子協議的名字;這是一個在創建WebSocket對象時,在參數protocols中指定的字符串。
- WebSocket.readyState:只讀屬性,返回當前WebSocket對象的鏈接狀態,可能的值為WebSocket中定義的常量:WebSocket.CONNECTING,WebSocket.OPEN,WebSocket.CLOSING,WebSocket.CLOSED。
- WebSocket.url:只讀屬性,返回值為當構造函數創建WebSocket實例對象時URL的絕對路徑。
- WebSocket.onopen:用于指定連接成功后的回調函數,當WebSocket的連接狀態readyState變為“OPEN”時調用;這意味著當前連接已經準備好發送和接受數據,這個事件處理程序通過事件(建立連接時)觸發。
- WebSocket.onclose:用于指定連接關閉后的回調函數,當WebSocket的連接狀態readyState變為“CLOSED”時被調用,它接收一個名字為“close”的CloseEvent事件對象。
- WebSocket.onmessage:用于指定當從服務器接受到信息時的回調函數,當從服務器收到一條消息時,該回調函數將被調用,在函數中接受一命名為“message”的MessageEvent事件對象。
- WebSocket.onerror:用于指定連接失敗后的回調函數,定義一個發生錯誤時執行的回調函數,此事件的事件名為"error"。
3.對象方法
WebSocket定義了2個方法:
(1)WebSocket.send(data):向服務器發送數據,將需要通過WebSocket連接傳輸至服務器的數據排入隊列,并根據所需要傳輸的數據字節的大小來增加屬性bufferedAmount的值 。若數據無法傳輸(例如數據需要緩存而緩沖區已滿)時,套接字會自行關閉。
參數data為傳輸至服務器的數據,它必須是以下類型之一:
- USVString:文本字符串。字符串將以UTF-8格式添加到緩沖區,并且屬性bufferedAmount將加上該字符串以UTF-8格式編碼時的字節數的值。
- ArrayBuffer:您可以使用一個有類型的數組對象發送底層二進制數據,其二進制數據內存將被緩存于緩沖區,屬性bufferedAmount將加上所需字節數的值。
- Blob:Blob類型將隊列blob中的原始數據以二進制傳輸,屬性bufferedAmount將加上原始數據的字節數的值。
- ArrayBufferView:以二進制幀的形式發送任何JavaScript類數組對象,其二進制數據內容將被隊列于緩沖區中,屬性bufferedAmount將加上對應字節數的值。
(2)WebSocket.close([code[, reason]]):關閉當前連接,如果連接已經關閉,則此方法不執行任何操作。
參數:
- code:可選,為一個數字狀態碼,它解釋了連接關閉的原因。如果沒有傳這個參數,默認使用1005。CloseEvent的允許的狀態碼見狀態碼列表。
- reason:可選,一個人類可讀的字符串,它解釋了連接關閉的原因,這個UTF-8編碼的字符串不能超過123個字節。
異常:
- INVALID_ACCESS_ERR:一個無效的code。
- SYNTAX_ERR:reason字符串太長(超過123字節)。
更多WebSockete API的詳細內容參見W3C的定義:The WebSocket API。
在客戶端使用WebSocket
如下為在網頁中使用原生WebSocket的實現方式。
var url = "ws://localhost:8080/websocket/text"; var ws = new WebSocket(url); ws.onopen = function(event) {console.log("websocket connection open.");console.log(event); };ws.onmessage = function(event) {console.log("websocket message received.")console.log(event.data); };ws.onclose = function (event) {console.log("websocket connection close.");console.log(event.code); };ws.onerror = function(event) {console.log("websocket connection error.");console.log(event); };在Web網頁中使用WebSocket需要瀏覽器支持,不同瀏覽器軟件版本對WebSocket的支持情況詳見瀏覽器兼容性。
另外,WebSocket客戶端除了可以在網頁中使用,目前還存在一些獨立的客戶端組件,如:
1.Jetty WebSocket Client API
2.websockets-api-java-spring-client
3.Java-WebSocket
在服務端使用WebSocket
在服務端使用WebSocket需要服務器組件支持,如下以在Tomcat 8.5.41(Tomcat 7之后才支持WebSocket)中使用原生WebSocket為例。
由于在服務端使用WebSocket需要使用到WebSocket的API,因此需要添加API依賴管理:
使用注解方式編寫WebSocket服務端:
@ServerEndpoint(value="/websocket/text") public class WebSocketTest {private static final Logger logger = LoggerFactory.getLogger(WsChatAnnotation.class);private static final AtomicInteger counter = new AtomicInteger(0); // 客戶端計數器private static final Set<WsChatAnnotation> connections = new CopyOnWriteArraySet<WsChatAnnotation>(); // 客戶端websocket連接集合private Session session = null; // WebSocket會話對象private Integer number = 0; // 客戶端編號public WsChatAnnotation() {number = counter.incrementAndGet();}/*** 客戶端建立websocket連接* @param session*/@OnOpenpublic void start(Session session) {logger.info("on open");this.session = session;connections.add(this);try {session.getBasicRemote().sendText(new StringBuffer().append("Hello: ").append(number).toString());} catch (IOException e) {e.printStackTrace();}}/*** 客戶端斷開websocket連接*/@OnClosepublic void close() {logger.info("session close");try {this.session.close();} catch (IOException e) {e.printStackTrace();} finally {connections.remove(this);}}/*** 接收客戶端發送的消息* @param message*/@OnMessagepublic void message(String message) {logger.info("message: {}", message);for(WsChatAnnotation client : connections) {synchronized (client) {try {client.session.getBasicRemote().sendText(message);} catch (IOException e) {e.printStackTrace();}}}}@OnErrorpublic void error(Throwable t) {logger.error("client: {} error", number, t.getMessage());} }反向代理對WebSocket的支持
當下的Web應用架構通常都是集群化部署,前端使用反向代理或者直接部署負載均衡器,這就要求反向代理或者負載均衡器必須支持WebSocket協議。
目前Nginx,Haporxy都已經支持WebSocket協議。
如下為在使用nginx作為反向代理的場景下,配置nginx代理websocket協議。
# add websocket proxy location ~ /ws {proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade";proxy_pass http://8080; }【參考】
https://spring.io/blog/2012/05/08/spring-mvc-3-2-preview-techniques-for-real-time-updates/ Spring MVC 3.2 Preview: Techniques for Real-time Updates
https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket#構造函數 WebSocket
https://www.cnblogs.com/chyingp/p/websocket-deep-in.html WebSocket協議:5分鐘從入門到精通
http://www.ruanyifeng.com/blog/2017/05/websocket.html WebSocket 教程
https://blog.csdn.net/chszs/article/details/26369257 Nginx擔當WebSockets代理
http://blog.fens.me/nodejs-websocket-nginx/ Nginx反向代理Websocket
總結
以上是生活随笔為你收集整理的WebSocket协议入门介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vb.net2019-多线程并行计算(5
- 下一篇: vb.net2019-多线程并行计算(6