WebSocket協議是基于TCP的一種新的網絡協議。它實現了瀏覽器與服務器全雙工(full-duplex)通信——允許服務器主動發送信息給客戶端。
WebSocket通信協議于2011年被IETF定為標準RFC 6455,并被RFC7936所補充規范。
一、WebSocket簡介
webSocket是什么:
1、WebSocket是一種在單個TCP連接上進行全雙工通信的協議
2、WebSocket使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據
3、在WebSocket API中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,并進行雙向數據傳輸
4、需要安裝第三方包:cmd中:go get -u -v github.com/gorilla/websocket
WebSocket 是一種標準協議,用于在客戶端和服務端之間進行雙向數據傳輸。但它跟 HTTP 沒什么關系,它是一種基于 TCP 的一種獨立實現。
以前客戶端想知道服務端的處理進度,要不停地使用 Ajax 進行輪詢,讓瀏覽器隔個幾秒就向服務器發一次請求,這對服務器壓力較高。另外一種輪詢就是采用 long poll 的方式,這就跟打電話差不多,沒收到消息就一直不掛電話,也就是說,客戶端發起連接后,如果沒消息,就一直不返回 Response 給客戶端,連接階段一直是阻塞的。
而 WebSocket 解決了 HTTP 的這幾個難題。首先,當服務器完成協議升級后( HTTP -> WebSocket ),服務端可以主動推送信息給客戶端,解決了輪詢造成的同步延遲問題。由于 WebSocket 只需要一次 HTTP 握手,服務端就能一直與客戶端保持通訊,直到關閉連接,這樣就解決了服務器需要反復解析 HTTP 協議,減少了資源的開銷。
WebSocket協議支持(在受控環境中運行不受信任的代碼的)客戶端與(選擇加入該代碼的通信的)遠程主機之間進行全雙工通信。用于此的安全模型是Web瀏覽器常用的基于原始的安全模式。 協議包括一個開放的握手以及隨后的TCP層上的消息幀。 該技術的目標是為基于瀏覽器的、需要和服務器進行雙向通信的(服務器不能依賴于打開多個HTTP連接(例如,使用XMLHttpRequest或和長輪詢))應用程序提供一種通信機制。
websocket 是一個基于應用層的網絡協議,建立在tcp 協議之上,和 http 協議可以說是兄弟的關系,但是這個兄弟有點依賴 http ,為什么這么說呢?我們都知道 HTTP 實現了三次握手來建立通信連接,實際上 websocket 的創始人很聰明,他不想重復的去造輪子,反正我兄弟已經實現了握手了,我干嘛還要重寫一套呢?先讓它去沖鋒陷陣呢,我坐收漁翁之利不是更香 嗎,所以一般來說,我們會先用 HTTP 先進行三次握手,再向服務器請求升級為websocket 協議,這就好比說,嘿兄弟你先去給我排個隊占個坑位建個小房子,到時候我在把這房子改造成摩天大樓。而且一般來說 80 和 443 端口一般 web 服務端都會外放出去,這樣可以有效的避免防火墻的限制。當然,你創建的 websocket 服務端進程的端口也需要外放出去。
很多人會想問,web開發 使用 HTTP 協議不是已經差不多夠用了嗎?為什么還要我再多學一種呢?這不是搞事情嘛,仔細想想,一門新技術的產生必然有原因的,如果沒有需求,我們干嘛那么蛋疼去寫那么多東西,就是因為 HTTP 這個協議有些業務需求支持太過于雞肋了,從 HTTP 0.9 到現在的 HTTP3.0 ,HTTP協議可以說說是在普通的web開發領域已經是十分完善且高效的了,說這個協議養活了全球半數的公司也不為過吧,像 2.0 服務器推送技術,3.0 采用了 UDP 而放棄了原來的 TCP ,這些改動都是為了進一步提升協議的性能,然而大家現在還是基本使用的 HTTP 1.1 這個最為經典的協議, 也是讓開發者挺尷尬的。
絕大多數的web開發都是應用層開發者,大多數都是基于已有的應用層去開發應用,可以說我們最熟悉、日常打交道最多的就是應用層協議了,底下 TCP/IP 協議我們基本很少會去處理,當然大廠可能就不一樣了,自己弄一套協議也是正常的,這大概也是程序員和碼農的區別吧,搬磚還是創新,差別還是很大的。網絡這種分層協議的好處我在之前的文章也說過了,這種隔離性很方便就可以讓我們基于原來的基礎去拓展,具有較好的兼容性。
總的來說,它就是一種依賴HTTP協議的,支持全雙工通信的一種應用層網絡協議。
二、WebSocket產生背景
簡單的說,WebSocket協議之前,雙工通信是通過多個http鏈接來實現,這導致了效率低下。WebSocket解決了這個問題。下面是標準RFC6455中的產生背景概述。
長久以來, 創建實現客戶端和用戶端之間雙工通訊的web app都會造成HTTP輪詢的濫用: 客戶端向主機不斷發送不同的HTTP呼叫來進行詢問。
這會導致一系列的問題:
- 1.服務器被迫為每個客戶端使用許多不同的底層TCP連接:一個用于向客戶端發送信息,其它用于接收每個傳入消息。
- 2.有些協議有很高的開銷,每一個客戶端和服務器之間都有HTTP頭。
- 3.客戶端腳本被迫維護從傳出連接到傳入連接的映射來追蹤回復。
一個更簡單的解決方案是使用單個TCP連接雙向通信。 這就是WebSocket協議所提供的功能。 結合WebSocket API ,WebSocket協議提供了一個用來替代HTTP輪詢實現網頁到遠程主機的雙向通信的方法。
WebSocket協議被設計來取代用HTTP作為傳輸層的雙向通訊技術,這些技術只能犧牲效率和可依賴性其中一方來提高另一方,因為HTTP最初的目的不是為了雙向通訊。
三、WebSocket實現原理
在實現websocket連線過程中,需要通過瀏覽器發出websocket連線請求,然后服務器發出回應,這個過程通常稱為“握手” 。**在 WebSocket API,瀏覽器和服務器只需要做一個握手的動作,然后,瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可以數據互相傳送。**在此WebSocket 協議中,為我們實現即時服務帶來了兩大好處:
四、WebSocket協議舉例
瀏覽器請求:
- GET /webfin/websocket/ HTTP/1.1。
- Host: localhost。
- Upgrade: websocket。
- Connection: Upgrade。
- Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==。
- Origin: http://服務器地址。
- Sec-WebSocket-Version: 13。
服務器回應:
- HTTP/1.1 101 Switching Protocols。
- Upgrade: websocket。
- Connection: Upgrade。
- Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=。
- WebSocket借用http請求進行握手,相比正常的http請求,多了一些內容。其中:
- Upgrade: websocket。
- Connection: Upgrade。
- 表示希望將http協議升級到Websocket協議。Sec-WebSocket-Key是瀏覽器隨機生成的base64 encode的值,用來詢問服務器是否是支持WebSocket。
服務器返回:
- Upgrade: websocket。
- Connection: Upgrade。
- 告訴瀏覽器即將升級的是Websocket協議
Sec-WebSocket-Accept是將請求包“Sec-WebSocket-Key”的值,與”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″這個字符串進行拼接,然后對拼接后的字符串進行sha-1運算,再進行base64編碼得到的。用來說明自己是WebSocket助理服務器。
Sec-WebSocket-Version是WebSocket協議版本號。RFC6455要求使用的版本是13,之前草案的版本均應當被棄用。
五、WebSocket使用
1.WebSocket 介紹
WebSocket 發起單個請求,服務端不需要等待客服端,客戶端在任何時候也能發消息到服務端,減少了輪詢時候的延遲.經歷一次連接后,服務器能給客戶端發多次。下圖是輪詢與WebSocket的區別。
基于http的實時消息是相當的復雜,在無狀態的請求中維持回話的狀態增加了復雜度,跨域也很麻煩,使用ajax處理請求有序請求需要考慮更多。通過ajax進行交流也不簡單。每一個延伸http功能的目的不是增加他的復雜度。websocket 可以大大簡化實時通信應用中的鏈接。
Websocket是一種底層網絡協議,可以讓你在這個基礎上建立別的標準協議。比如在WebSocket的客戶端的基礎上使用XMPP登錄不同的聊天服務器,因為所有的XMPP服務理解相同的標準協議。WebSocket是web應用的一種創新。
為了與其他平臺競爭,WebSocket是H5應用提供的一部分先進功能。每個操作系統都需要網絡功能,能夠讓應用使用Sockets與別的主機進行通信,是每個大平臺的核心功能。在很多方面,讓Web應用表現的像操作系統平臺是html5的趨勢。像socket這樣底層的網絡協議APIs不會符合原始的安全模型,也不會有web api那樣的設計風格。WebSocket給H5應用提供TCP的方式不會消弱網絡安全且有現代的Api。
WebSocket是Html5平臺的一個重要組件也是開發者強有力的工具。簡單的說,你需要WebSocket創建世界級的web應用。它彌補了http不適合實時通信的重大缺陷。異步、雙向通信模式,通過傳輸層協議使WebSocket具有普遍靈活性。想象一下你能用WebSocket創建正真實實時應用的所有方式。比如聊天、協作文檔編輯、大規模多人在線游戲(MMO),股票交易應用等等。
WebSocket是一個協議,但也有一個WebSocket API,這讓你的應用去控制WebSocket的協議去響應被服務端觸發的事件。API是W3C開發,協議是IETE制定。現代瀏覽器支持WebSocket API,這包括使用全雙工和雙向鏈接的方法和特性。讓你執行像打開關閉鏈接、發送接收消息、監聽服務端事件等必要操作。
2.WebSocket API
WebSocket API其實就是一個使用WebSocket協議的接口,通過它來建立全雙工通道來收發消息,簡單易學,要連接遠程服務器,只需要創建一個WebSocket對象實體,并傳入一個服務端的URL。在客戶端和服務端一開始握手的期間,http協議升級到WebSocket協議就建立了連接,底層都是TCP協議。一旦建立連接,通過WebSocket接口可以反復的發送消息。在你的代碼里面,你可以使用異步事件監聽連接生命周期的每個階段。
WebSocket API是純事件驅動,一旦建立全雙工連接,當服務端給客戶端發送數據或者資源,它能自動發送狀態改變的數據和通知。所以你不需要為了狀態的更新而去輪訓Server,在客戶端監聽即可。
首先,我們需要通過調用WebSocket構造函數來創建一個WebSocket連接,構造函數會返回一個WebSocket實例,可以用來監聽事件。這些事件會告訴你什么時候連接建立,什么時候消息到達,什么時候連接關閉了,以及什么時候發生了錯誤。WebSocket協議定義了兩種URL方案,WS和WSS分別代表了客戶端和服務端之間未加密和加密的通信。WS(WebSocket)類似于Http URL,而WSS(WebSocket Security)URL 表示連接是基于安全傳輸層(TLS/SSL)和https的連接是同樣的安全機制。
WebSocket的構造函數需要一個URL參數和一個可選的協議參數(一個或者多個協議的名字),協議的參數例如XMPP(Extensible Messaging and Presence Protocol)、SOAP(Simple Object Access Protocol)或者自定義協議。而URL參數需要以WS://或者WSS://開頭,例如:ws://www.websocket.org,如果URL有語法錯誤,構造函數會拋出異常。
var ws = new WebSocket("ws://www.websocket.org");
//測試了下鏈接不上。
第二個參數是協議名稱,是可選的,服務端和客服端使用的協議必須一致,這樣收發消息彼此才能理解,你可以定義一個或多個客戶端使用的協議,服務端會選擇一個來使用,一個客服端和一個服務端之間只能有一個協議。當然都得基于WebSocket,WebSocket的重大好處之一就是基于WebSocket協議的廣泛使用,讓你的Web能夠擁有傳統桌面程序那樣的能力。
言歸正傳,我們回到構造函數,在第一次握手之后,和協議的名稱一起,客戶端會發送一個Sec-WebSocket-Protocol 頭,服務端會選擇0個或一個協議,響應會帶上同樣的Sec-WebSocket-Protocol 頭,否則會關閉連接。通過協議協商(Protocol negotiation ),我們可以知道給定的WebSocket服務器所支持的協議和版本,然后應用選擇協議使用。
var ws = new WebSocket("ws://echo.websocket.org", "myProtocol");
//myProtocol 是假設的一個定義好的且符合標準的協議。
你可以傳遞一個協議的數組。
//服務端會選擇其中一個使用
echoSocket.onopen = function(e) {
// Check the protocol chosen by the server
console.log(echoSocket.protocol);
}
輸出:com.kaazing.ech
協議這個參數有三種。
1.注冊協議:根據RFC6455(WebSocket 協議)和IANA被官方注冊的標準協議。例如 微軟的SOAP。
看到兩個華為的:
2.開放協議:被廣泛使用的標注協議,例如XMPP和STOMP。但沒有被正式注冊。
3.自定義協議:自己編寫和使用的WebSocket的協議。 協議會再后續章節給出詳細介紹,下面先看事件、對象和方法以及實例。
3.WebSocket事件
WebSocket API是純事件驅動,通過監聽事件可以處理到來的數據和改變的鏈接狀態。客戶端不需要為了更新數據而輪訓服務器。服務端發送數據后,消息和事件會異步到達。WebSocket編程遵循一個異步編程模型,只需要對WebSocket對象增加回調函數就可以監聽事件。你也可以使用addEventListener()方法來監聽。而一個WebSocket對象分四類不同事件。
1.open
一旦服務端響應WebSocket連接請求,就會觸發open事件。響應的回調函數稱為onopen。
ws.onopen = function(e) {
console.log("Connection open...");
};
open事件觸發的時候,意味著協議握手結束,WebSocket已經準備好收發數據。如果你的應用收到open事件,就可以確定服務端已經處理了建立連接的請求,且同意和你的應用通信。
2.Message
當消息被接受會觸發消息事件,響應的回調函數叫做onmessage。如下:
ws.onmessage = function(e) {
if(typeof e.data === "string"){
console.log("String message received", e, e.data);
} else {
console.log("Other message received", e, e.data);
}
};
除了文本消息,WebSocket消息機制還能處理二進制數據,有Blob和ArrayBuffer兩種類型,在讀取到數據之前需要決定好數據的類型。
ws.binaryType = "blob";
// Event handler for receiving Blob messages
ws.onmessage = function(e) {
if(e.data instanceof Blob){
console.log("Blob message received", e.data);
var blob = new Blob(e.data);
}
};
ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {
if(e.data instanceof ArrayBuffer){
console.log("ArrayBuffer Message Received", + e.data);
// e.data即ArrayBuffer類型
var a = new Uint8Array(e.data);
}
};
3.Error
如果發生意外的失敗會觸發error事件,相應的函數稱為onerror,錯誤會導致連接關閉。如果你收到一個錯誤事件,那么你很快會收到一個關閉事件,在關閉事件中也許會告訴你錯誤的原因。而對錯誤事件的處理比較適合做重連的邏輯。
ws.onerror = function(e) {
console.log("WebSocket Error: " , e);
//Custom function for handling errors
handleErrors(e);
};
4.Close
不言而喻,當連接關閉的時候回觸發這個事件,對應onclose方法,連接關閉之后,服務端和客戶端就不能再收發消息。
WebSocket的規范其實還定義了ping和pong 架構(frames),可以用來做keep-alive,心跳,網絡狀態查詢,latency instrumentation(延遲儀表?),但是目前 WebSocket API還沒有公布這些特性,盡管瀏覽器支持了ping,但不會觸發ping事件,相反,瀏覽器會自動響應pong,第八章會將更多關于ping和pong的細節。
當然你可以調用close方法斷開與服務端的鏈接來觸發onclose事件:
console.log("Connection closed", e);
};
連接失敗和成功的關閉握手都會觸發關閉事件,WebSocket的對象的readyState屬性就代表連接的狀態(2代表正在關閉,3代表已經關閉)。關閉事件有三個屬性可以用來做異常處理和重獲: wasClean,code和reason。wasClean是一個bool值,代表連接是否干凈的關閉。 如果是響應服務端的close事件,這個值為true,如果是別的原因,比如因為是底層TCP連接關閉,wasClean為false。code和reason代表關閉連接時服務端發送的狀態,這兩個屬性和給入close方法的code和reason參數是對應的,稍后會描述細節。
4.WebSocket 方法
WebSocket 對象有兩個方法:send()和close()。
1.send()
一旦在服務端和客戶端建立了全雙工的雙向連接,可以使用send方法去發送消息。
ws.send("Hello WebSocket!");
當連接是open的時候send()方法傳送數據,當連接關閉或獲取不到的時候回拋出異常。一個通常的錯誤是人們喜歡在連接open之前發送消息。如下所示:
var ws = new WebSocket("ws://echo.websocket.org")
ws.send("Initial data");
正確的姿勢如下,應該等待open事件觸發后再發送消息。
ws.onopen = function(e) {
ws.s
如果想通過響應別的事件去發送消息,可以檢查readyState屬性的值為open的時候來實現。
if (ws.readyState === WebSocket.OPEN) {
//open的時候即可發送
ws.send(data);
} else {
// Do something else in this case.
//Possibly ignore the data or enqueue it.
}
}
發送二進制數據:
var blob = new Blob("blob contents");
ws.send(blob);
// Send an ArrayBuffer
var a = new Uint8Array([8,6,7,5,3,0,9]);
ws.send(a.buffer);
Blob對象和JavaScript File API一起使用的時候相當有用,可以發送或接受文件,大部分的多媒體文件,圖像,視頻和音頻文件。這一章末尾會結合File API提供讀取文件內容來發送WebSocket消息的實例代碼。
2.close()
使用close方法來關閉連接,如果連接以及關閉,這方法將什么也不做。調用close方法只后,將不能發送數據。
close方法可以傳入兩個可選的參數,code(numerical)和reason(string),以告訴服務端為什么終止連接。第三章講到關閉握手的時候再詳細討論這兩個參數。
ws.close(1000, "Closing normally");
//1000是狀態碼,代表正常結束。
5.WebSocket 屬性
WebSocket對象有三個屬性,readyState,bufferedAmount和Protocol。
1.readyState
WebSocket對象通過只讀屬性readyState來傳達連接狀態,它會更加連接狀態自動改變。下表展示了readyState屬性的四個不同的值。
了解當前連接的狀態有助于我們調試。
2.bufferedAmount
有時候需要檢查傳輸數據的大小,尤其是客戶端傳輸大量數據的時候。雖然send()方法會馬上執行,但數據并不是馬上傳輸。瀏覽器會緩存應用流出的數據,你可以使用bufferedAmount屬性檢查已經進入隊列但還未被傳輸的數據大小。這個值不包含協議框架、操作系統緩存和網絡軟件的開銷。
下面這個例子展示了如何使用bufferedAmount屬性每秒更新發送。如果網絡不能處理這個頻率,它會自適應。
var THRESHOLD = 10240;
//建立連接
var ws = new WebSocket("ws://echo.websocket.org");
// Listen for the opening event
ws.onopen = function () {
setInterval( function() {
//緩存未滿的時候發送
if (ws.bufferedAmount < THRESHOLD) {
ws.send(getApplicationState());
}
}, 1000);
};
//使用bufferedAmount屬性發送數據可以避免網絡飽和。
3.protocol
在構造函數中,protocol參數讓服務端知道客戶端使用的WebSocket協議。而WebSocket對象的這個屬性就是指的最終服務端確定下來的協議名稱,當服務端沒有選擇客戶端提供的協議或者在連接握手結束之前,這個屬性都是空的。
完整實例:
現在我們已經過了一遍WebSocket的構造函數、事件、屬性和方法,接下來通過一個完整的實例來學習WebSocket API。實例使用“Echo”服務器:ws://echo.websocket.org,它能夠接受和返回發過去的數據。這樣有助于理解WebSocket API是如何和服務器交互的。
首先,我們先建立連接,讓頁面展示客戶端連接服務端的信息,然后發送、接受消息,最后關閉連接。
Websocket Echo Client
function setup() {
output = document.getElementById("output");
ws = new WebSocket("ws://echo.websocket.org/echo");
// 監聽open
ws.onopen = function (e) {
log("Connected");
sendMessage("Hello WebSocket!");
}
// 監聽close
ws.onclose = function (e) {
log("Disconnected: " + e.reason);
}
//監聽errors
ws.onerror = function (e) {
log("Error ");
}
// 監聽 messages
ws.onmessage = function (e) {
log("Message received: " + e.data);
//收到消息后關閉
ws.close();
}
}
// 發送消息
function sendMessage(msg) {
ws.send(msg);
log("Message sent");
}
// logging
function log(s) {
var p = document.createElement("p");
p.style.wordWrap = "break-word";
p.textContent = s;
output.appendChild(p);
// Also log information on the javascript console
console.log(s);
}
// Start
setup();
判斷瀏覽器是否支持:
console.log("This browser supports WebSocket!");
} else {
console.log("This browser does not support WebSocket.");
}
-
服務器
+關注
關注
13文章
9777瀏覽量
87804 -
瀏覽器
+關注
關注
1文章
1040瀏覽量
36248 -
WebSocket
+關注
關注
0文章
30瀏覽量
4094
發布評論請先 登錄
如何移植libwebsockets

labview TCP 發送問題
【迪文COF結構智能屏試用體驗】3. 結合esp32搭建聊天室
iOS系統SRWebSocket的源碼解析上
根據WebSocket協議完全使用C++實現函數
如何使用SpringBoot集成Netty開發一個基于WebSocket的聊天室說明

為什么客戶不直接控制智能家居網關,而要使用云服務器做中轉?

有了HTTP為什么還要有websocket協議?
鴻蒙上WebSocket的使用方法
為什么有了HTTP,還需要WebSocket協議?

評論