WebSocket是HTML5提供的一種在單個(gè)TCP連接上進(jìn)行全雙工通信(雙向通信)的協(xié)議,WebSocket使客戶(hù)端和服務(wù)端之間的數(shù)據(jù)交換變得更加簡(jiǎn)單,允許服務(wù)端主動(dòng)向客戶(hù)端推送數(shù)據(jù)。
Web中已經(jīng)有了HTTP協(xié)議,為什么還需要WebSocket協(xié)議呢?
由于HTTP協(xié)議有一個(gè)天生的缺陷:通信只能由客戶(hù)端發(fā)起,因此HTTP協(xié)議做不到服務(wù)器主動(dòng)向客戶(hù)端推送信息。

HTTP這種單向請(qǐng)求的特點(diǎn),注定了如果服務(wù)器有連續(xù)的狀態(tài)變化,客戶(hù)端要獲知就必須采用輪詢(xún)的方式。而輪詢(xún)的效率低下且非常浪費(fèi)資源,因?yàn)楸仨毑煌5剡B接或者讓HTTP連接始終打開(kāi)。此時(shí),也就出現(xiàn)了WebSocket。

WebSocket協(xié)議誕生于2008年,2011年成為國(guó)際標(biāo)準(zhǔn),主流瀏覽器均已經(jīng)得到支持。WebSocket協(xié)議最大的特點(diǎn)是服務(wù)器可以主動(dòng)的向客戶(hù)端推送信息,客戶(hù)端也可以主動(dòng)的向服務(wù)器發(fā)送信息,它是真正的雙向平等對(duì)話(huà),屬于服務(wù)器推送技術(shù)的一種。
WebSocket協(xié)議的特點(diǎn)
- WebSocket協(xié)議建立在TCP協(xié)議之上,服務(wù)器實(shí)現(xiàn)比較容易。
- WebSocket協(xié)議與HTTP協(xié)議有著良好的兼容性,默認(rèn)端口是80和443,握手階段采用HTTP協(xié)議因此不容易屏蔽,能通過(guò)各種HTTP代理服務(wù)器。
- WebSocket數(shù)據(jù)格式比較輕量,性能開(kāi)銷(xiāo)較小,通信高效。
- WebSocket可以發(fā)送文本也可以發(fā)送二進(jìn)制數(shù)據(jù)
- WebSocket沒(méi)有同源限制,客戶(hù)端可以與任意服務(wù)器通信。
- WebSocket協(xié)議標(biāo)識(shí)符是
ws或wss,服務(wù)器地址直接是URL。

WebSocket API
WebSocket包含網(wǎng)絡(luò)協(xié)議與API,讓用戶(hù)能夠在客戶(hù)端和服務(wù)端創(chuàng)建WebSocket連接。WebSocketAPI是使用WebSocket協(xié)議的接口,通過(guò)它來(lái)創(chuàng)建全雙工通道以收發(fā)消息。
WebSocketAPI是純事件驅(qū)動(dòng),一旦建立全雙工鏈接,當(dāng)服務(wù)端給客戶(hù)端發(fā)送數(shù)據(jù)或資源時(shí)能自動(dòng)發(fā)送狀態(tài)改變的數(shù)據(jù)和通知。所以不需要為了狀態(tài)的更新而去輪詢(xún)服務(wù)器,只需要在客戶(hù)端監(jiān)聽(tīng)即可。
在WebSocket的API中,客戶(hù)端和服務(wù)端只需要完成一次握手動(dòng)作,然后客戶(hù)端與服務(wù)端之間就會(huì)形成一條快速通道,兩者之間就可以直接創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)據(jù)傳輸。
客戶(hù)端通過(guò)JavaScript向服務(wù)端發(fā)起建立WebSocket連接的握手請(qǐng)求,當(dāng)連接建立成功以后,客戶(hù)端和服務(wù)端就可以通過(guò)TCP連接直接交換數(shù)據(jù),當(dāng)獲取WebSocket連接后,可通過(guò)send()方法來(lái)向服務(wù)端發(fā)送數(shù)據(jù),客戶(hù)端通過(guò)onmessage事件來(lái)接收服務(wù)端返回的數(shù)據(jù)。
WebSocket協(xié)議本質(zhì)
WebSocket協(xié)議本質(zhì)上是一個(gè)基于TCP的協(xié)議 ,為了建立一個(gè)WebSocket連接,客戶(hù)端瀏覽器首先要向服務(wù)器發(fā)起一個(gè)HTTP握手請(qǐng)求,這個(gè)請(qǐng)求和通常的HTTP請(qǐng)求有所不同,它包含了一些附加的頭信息,其中會(huì)附加頭信息Upgrade: WebSocket表明這是一個(gè)申請(qǐng)協(xié)議升級(jí)的HTTP請(qǐng)求。服務(wù)器解析這些附加的頭信息后會(huì)產(chǎn)生應(yīng)答信息并返回給客戶(hù)端,客戶(hù)端和服務(wù)器的WebSocket連接就會(huì)建立起來(lái),雙方就可以通過(guò)連接通道自由的傳遞信息,并且這個(gè)連接會(huì)持續(xù)存在直到客戶(hù)端或服務(wù)器的某一方主動(dòng)關(guān)閉連接。
客戶(hù)端發(fā)送的典型WebSocket握手請(qǐng)求
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13
服務(wù)器回應(yīng)的WebSocket典型握手響應(yīng)
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/
WebSocket專(zhuān)有字段分析
Upgrade: websocket
Upgrade字段必須設(shè)置為websocket,表示客戶(hù)端希望連接升級(jí)到websocket協(xié)議。
Connection: Upgrade
Connection必須設(shè)置為Upgrade,表示客戶(hù)端希望連接升級(jí)
Sec-WebSocket-Key
Sec-WebSocket-Key是隨機(jī)字符串,服務(wù)器會(huì)使用它來(lái)構(gòu)造出一個(gè)SHA-1的信息摘要,將Sec-WebSocket-Key加上一個(gè)特殊的字符串,然后計(jì)算出SHA-1摘要,之后在進(jìn)行BASE-64編碼,最終結(jié)果作為Sec-WebSocket-Accept頭的值返回給客戶(hù)端。如此操作,可以盡量避免普通HTTP請(qǐng)求被誤認(rèn)為WebSocket協(xié)議。
Sec-WebSocket-Version
Sec-WebSocket-Version表示支持的WebSocket版本,RFC6455要求使用的版本是13,之前草案的版本均應(yīng)當(dāng)棄用。
Origin
Origin字段是可選的,通常用來(lái)表示在瀏覽器中發(fā)起此WebSocket連接所在的頁(yè)面,類(lèi)似于Referer。但是與Referer不同的是,Origin只包含了協(xié)議和主機(jī)名稱(chēng)。
WebSocket與 Socket有什么區(qū)別呢?
在軟件通信的七層架構(gòu)中下三層結(jié)構(gòu)偏向于數(shù)據(jù)通信,上三層偏向于數(shù)據(jù)處理,中間的傳輸層則是連接上三層和下三層之間的橋梁,每一層都做著不同的工作,上層協(xié)議 依賴(lài)于下層協(xié)議。
Socket其實(shí)并不是一個(gè)協(xié)議,而是應(yīng)用層與TCP/IP協(xié)議簇通信的中間軟件抽象層,它是一組接口。當(dāng)兩臺(tái)主機(jī)通信的時(shí)候,會(huì)讓Socket去組織數(shù)據(jù)以符合指定的協(xié)議。TCP連接則更依賴(lài)于底層的IP協(xié)議,IP協(xié)議的連接則依賴(lài)于鏈路層等更低層次。
WebSocket則是一個(gè)典型的應(yīng)用層協(xié)議,總體而言,Socket是傳輸控制層協(xié)議,WebSocket則是應(yīng)用層協(xié)議。
構(gòu)造函數(shù)
首先需要通過(guò)調(diào)用WebSocket的構(gòu)造函數(shù)來(lái)創(chuàng)建WebSocket連接,構(gòu)造函數(shù)會(huì)返回一個(gè)WebSocket實(shí)例,用來(lái)監(jiān)聽(tīng)事件。
WebSocket構(gòu)造函數(shù)需要傳入一個(gè)URL參數(shù)和一個(gè)可選的協(xié)議參數(shù)
var ws = new WebSocket(url, [protocol]);
console.log(ws);

構(gòu)造函數(shù)的第一個(gè)參數(shù)是URL,WebSocket協(xié)議定義了兩種URL方案,WS和WSS分別代表了客戶(hù)端和服務(wù)端之間未加密的通信。WS(WebSocket)類(lèi)似于HTTP的URL,WSS(WebSocket Security)的URL表示連接是基于安全傳輸層(TLS/SSL),和HTTPS的連接使用的是同樣的安全機(jī)制。URL參數(shù)需要以ws://或wss://開(kāi)頭,如果URL存在語(yǔ)法錯(cuò)誤構(gòu)造函數(shù)會(huì)拋出異常。
構(gòu)造函數(shù)的第二個(gè)參數(shù)是協(xié)議名稱(chēng),是可選的,服務(wù)端和客戶(hù)端使用的協(xié)議必須保持一致,這樣收發(fā)消息時(shí)彼此才能理解。雖然可以定義一個(gè)或多個(gè)客戶(hù)端使用的協(xié)議,但服務(wù)端只會(huì)選擇一個(gè)來(lái)使用,一個(gè)客戶(hù)端和一個(gè)服務(wù)端只能有一個(gè)協(xié)議,而且都必須基于WebSocket。協(xié)議名稱(chēng)參數(shù)支持XMPP(Extensible Messaging and Presence Protocol)、SOAP(Simple Object Access Protocol)或自定義協(xié)議。
WebSocket對(duì)象的屬性
ws.readyState
只讀屬性readState表示當(dāng)前連接狀態(tài),可選值包括
- 0
WebSocket.CONNECTING表示連接正在建立中但尚未建立 - 1
WebSocket.OPEN表示連接已經(jīng)建立可以發(fā)送消息進(jìn)行通信 - 2
WebSocket.CLOSING表示連接正在進(jìn)行關(guān)閉握手 - 3
WebSocket.CLOSED表示連接已經(jīng)關(guān)閉或連接不能打開(kāi)
ws.bufferedAmount
只讀屬性bufferedAmount表示已經(jīng)被send()放入正在隊(duì)列中等待傳輸,但還沒(méi)有發(fā)出的UTF-8文本字節(jié)數(shù)。當(dāng)需要檢查傳輸數(shù)據(jù)大小時(shí),尤其是客戶(hù)端傳輸大量數(shù)據(jù)時(shí),雖然send()方法會(huì)馬上執(zhí)行,但數(shù)據(jù)并不是馬上傳輸。瀏覽器會(huì)緩存應(yīng)用流出的數(shù)據(jù),可以使用bufferedAmount屬性檢查已經(jīng)進(jìn)入隊(duì)列但未被傳輸?shù)臄?shù)據(jù)大小。bufferedAmount值不包括協(xié)議框架、操作系統(tǒng)緩存和網(wǎng)絡(luò)軟件的開(kāi)銷(xiāo)。
例如:使用bufferedAmount屬性每秒更新發(fā)送,如果網(wǎng)絡(luò)不能處理這個(gè)頻率會(huì)自動(dòng)適應(yīng)。
//判斷瀏覽器是否支持WebSocket
if(window.WebSocket)
{
console.log("This browser supports WebSocket");
}
else
{
console.log("This browser does not support WebSocket");
}
//10K
var THRESHOLD = 10240;
//建立連接
var url = "ws://echo.websocket.org";
var ws = new WebSocket(url);
//監(jiān)聽(tīng)握手請(qǐng)求
ws.onopen = function()
{
setInterval(function(){
//如果緩存未滿(mǎn)時(shí)發(fā)送數(shù)據(jù),使用bufferedAmount屬性發(fā)送數(shù)據(jù)可以避免網(wǎng)絡(luò)飽和。
if(ws.bufferedAmount < THRESHOLD){
ws.send(getApplicationState());
}
}, 1000);
}
ws.protocol
在構(gòu)造函數(shù)中protocol參數(shù)會(huì)讓服務(wù)器知道客戶(hù)端使用的WebSocket協(xié)議,WebSocket對(duì)象的protocol屬性是指最終服務(wù)器確定下來(lái)的協(xié)議名稱(chēng),當(dāng)服務(wù)器沒(méi)有選擇客戶(hù)端提供的協(xié)議或在連接握手之前,此屬性為空。