舉例來說,我們想了解今天的天氣,只能是客戶端向服務(wù)器發(fā)出請求,服務(wù)器返回查詢結(jié)果。HTTP 協(xié)議做不到服務(wù)器主動向客戶端推送信息。
WebSocket 協(xié)議在2008年誕生,2011年成為國際標(biāo)準(zhǔn)。所有瀏覽器都已經(jīng)支持了。它的最大特點(diǎn)就是,服務(wù)器可以主動向客戶端推送信息,客戶端也可以主動向服務(wù)器發(fā)送信息,是真正的雙向平等對話,屬于服務(wù)器推送技術(shù)的一種。
一、傳統(tǒng)的實(shí)現(xiàn)即時通信的方式
1、ajax輪詢
ajax輪詢的原理非常簡單,讓瀏覽器隔個幾秒就發(fā)送一次請求,詢問服務(wù)器是否有新信息。
場景再現(xiàn):
客戶端:啦啦啦,有沒有新信息(Request)
服務(wù)端:沒有(Response)
客戶端:啦啦啦,有沒有新信息(Request)
服務(wù)端:沒有。。(Response)
客戶端:啦啦啦,有沒有新信息(Request)
服務(wù)端:你好煩啊,沒有啊。。(Response)
客戶端:啦啦啦,有沒有新消息(Request)
服務(wù)端:好啦好啦,有啦給你。(Response)
客戶端:啦啦啦,有沒有新消息(Request)
服務(wù)端:。。。。。沒。。。。沒。。。沒有(Response) —- loop
2、long poll
long poll
其實(shí)原理跟 ajax輪詢
差不多,都是采用輪詢的方式,不過采取的是阻塞模型(一直打電話,沒收到就不掛電話),也就是說,客戶端發(fā)起連接后,如果沒消息,就一直不返回Response給客戶端。直到有消息才返回,返回完之后,客戶端再次建立連接,周而復(fù)始。
場景再現(xiàn):
客戶端:啦啦啦,有沒有新信息,沒有的話就等有了才返回給我吧(Request)
服務(wù)端:額。。 等待到有消息的時候。。來 給你(Response)
客戶端:啦啦啦,有沒有新信息,沒有的話就等有了才返回給我吧(Request) -loop
從上面可以看出其實(shí)這兩種方式,都是在不斷地建立HTTP連接,然后等待服務(wù)端處理,可以體現(xiàn)HTTP協(xié)議的另外一個特點(diǎn),被動性。何為被動性呢,其實(shí)就是,服務(wù)端不能主動聯(lián)系客戶端,只能有客戶端發(fā)起。
小結(jié):ajax輪詢 需要服務(wù)器有很快的處理速度和資源。(速度)
long poll 需要有很高的并發(fā),也就是說同時接待客戶的能力。(場地大?。?/blockquote>3、長連接
在頁面里嵌入一個隱蔵iframe,將這個隱蔵iframe的src屬性設(shè)為對一個長連接的請求或是采用xhr請求,服務(wù)器端就能源源不斷地往客戶端輸入數(shù)據(jù)。
優(yōu)點(diǎn):消息即時到達(dá),不發(fā)無用請求;管理起來也相對方便。
缺點(diǎn):服務(wù)器維護(hù)一個長連接會增加開銷,當(dāng)客戶端越來越多的時候,server壓力大!
實(shí)例:Gmail聊天
(1)基于http協(xié)議的長連接
在HTTP1.0和HTTP1.1協(xié)議中都有對長連接的支持。其中HTTP1.0需要在request中增加”Connection: keep-alive“ header才能夠支持,而HTTP1.1默認(rèn)支持.
http1.0請求與服務(wù)端的交互過程:
- a)客戶端發(fā)出帶有包含一個header:”Connection: keep-alive“的請求
- b)服務(wù)端接收到這個請求后,根據(jù)http1.0和”Connection: keep-alive“判斷出這是一個長連接,就會在response的header中也增加”Connection: keep-alive“,同是不會關(guān)閉已建立的tcp連接.
- c)客戶端收到服務(wù)端的response后,發(fā)現(xiàn)其中包含”Connection: keep-alive“,就認(rèn)為是一個長連接,不關(guān)閉這個連接。并用該連接再發(fā)送request.轉(zhuǎn)到a)
(2)http1.1請求與服務(wù)端的交互過程
- a)客戶端發(fā)出http1.1的請求
- b)服務(wù)端收到http1.1后就認(rèn)為這是一個長連接,會在返回的response設(shè)置Connection: keep-alive,同是不會關(guān)閉已建立的連接.
- c)客戶端收到服務(wù)端的response后,發(fā)現(xiàn)其中包含”Connection: keep-alive“,就認(rèn)為是一個長連接,不關(guān)閉這個連接。并用該連接再發(fā)送request.轉(zhuǎn)到a)
基于http協(xié)議的長連接減少了請求,減少了建立連接的時間,但是每次交互都是由客戶端發(fā)起的,客戶端發(fā)送消息,服務(wù)端才能返回客戶端消息.因?yàn)榭蛻舳艘膊恢婪?wù)端什么時候會把結(jié)果準(zhǔn)備好,所以客戶端的很多請求是多余的,僅是維持一個心跳,浪費(fèi)了帶寬.
4、Flash Socket
在頁面中內(nèi)嵌入一個使用了Socket類的 Flash 程序JavaScript通過調(diào)用此Flash程序提供的Socket接口與服務(wù)器端的Socket接口進(jìn)行通信,JavaScript在收到服務(wù)器端傳送的信息后控制頁面的顯示。
優(yōu)點(diǎn):實(shí)現(xiàn)真正的即時通信,而不是偽即時。
缺點(diǎn):客戶端必須安裝Flash插件,移動端支持不好,IOS系統(tǒng)中沒有flash的存在;非HTTP協(xié)議,無法自動穿越防火墻。
二、websocket的方式實(shí)現(xiàn)服務(wù)端消息推送
1、什么是socket?什么是websocket?兩者有什么區(qū)別?websocket是僅僅將socket的概念移植到瀏覽器中的實(shí)現(xiàn)嗎?
我們知道,在網(wǎng)絡(luò)中的兩個應(yīng)用程序(進(jìn)程)需要全雙工相互通信(全雙工即雙方可同時向?qū)Ψ桨l(fā)送消息),需要用到的就是socket,它能夠提供端對端通信,對于程序員來講,他只需要在某個應(yīng)用程序的一端(暫且稱之為客戶端)創(chuàng)建一個socket實(shí)例并且提供它所要連接一端(暫且稱之為服務(wù)端)的IP地址和端口,而另外一端(服務(wù)端)創(chuàng)建另一個socket并綁定本地端口進(jìn)行監(jiān)聽,然后客戶端進(jìn)行連接服務(wù)端,服務(wù)端接受連接之后雙方建立了一個端對端的TCP連接,在該連接上就可以雙向通訊了,而且一旦建立這個連接之后,通信雙方就沒有客戶端服務(wù)端之分了,提供的就是端對端通信了。我們可以采取這種方式構(gòu)建一個桌面版的im程序,讓不同主機(jī)上的用戶發(fā)送消息。從本質(zhì)上來說,socket并不是一個新的協(xié)議,它只是為了便于程序員進(jìn)行網(wǎng)絡(luò)編程而對tcp/ip協(xié)議族通信機(jī)制的一種封裝。
socket傳送門: http://therapist.net.cn/ask/thread/30945
websocket是html5規(guī)范中的一個部分,它借鑒了socket這種思想,為web應(yīng)用程序客戶端和服務(wù)端之間(注意是客戶端服務(wù)端)提供了一種全雙工通信機(jī)制。同時,它又是一種新的應(yīng)用層協(xié)議,websocket協(xié)議是為了提供web應(yīng)用程序和服務(wù)端全雙工通信而專門制定的一種應(yīng)用層協(xié)議,通常它表示為:ws://echo.websocket.org/?encoding=text HTTP/1.1,可以看到除了前面的協(xié)議名和http不同之外,它的表示地址就是傳統(tǒng)的url地址。
Websocket其實(shí)是一個新協(xié)議,跟HTTP協(xié)議基本沒有關(guān)系,只是為了兼容現(xiàn)有瀏覽器的握手規(guī)范而已,也就是說它是HTTP協(xié)議上的一種補(bǔ)充可以通過這樣一張圖理解
websocket具有以下幾個方面的優(yōu)勢:
- (1)建立在 TCP 協(xié)議之上,服務(wù)器端的實(shí)現(xiàn)比較容易。
- (2)與 HTTP 協(xié)議有著良好的兼容性。默認(rèn)端口也是80和443,并且握手階段采用 HTTP 協(xié)議,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務(wù)器。
- (3)數(shù)據(jù)格式比較輕量,性能開銷小,通信高效。
- (4)可以發(fā)送文本,也可以發(fā)送二進(jìn)制數(shù)據(jù)。
- (5)沒有同源限制,客戶端可以與任意服務(wù)器通信。
- (6)協(xié)議標(biāo)識符是
ws
(如果加密,則為wss
),服務(wù)器網(wǎng)址就是 URL。2、websocket的通信原理和機(jī)制
websocket傳輸使用的協(xié)議如下圖:
Websocket是一個應(yīng)用層協(xié)議,它必須依賴 HTTP 協(xié)議進(jìn)行一次握手 ,握手成功后,數(shù)據(jù)就直接從 TCP 通道傳輸,與 HTTP 無關(guān)了。即:websocket分為握手和數(shù)據(jù)傳輸階段,即進(jìn)行了HTTP握手 + 雙工的TCP連接。既然是基于瀏覽器端的web技術(shù),那么它的通信肯定少不了http,websocket本身雖然也是一種新的應(yīng)用層協(xié)議,但是它也不能夠脫離http而單獨(dú)存在。具體來講,我們在客戶端構(gòu)建一個websocket實(shí)例,并且為它綁定一個需要連接到的服務(wù)器地址,當(dāng)客戶端連接服務(wù)端的時候,會向服務(wù)端發(fā)送一個類似下面的http報文
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Version: 13
客戶端發(fā)起的WebSocket連接報文類似傳統(tǒng)HTTP報文,其中:
- Upgrade: websocket
Connection: Upgrade
這個是WebSocket的核心,告訴服務(wù)器,客戶端發(fā)起的是WebSocket類型請求。Sec-WebSocket-Key是WebSocket客戶端發(fā)送的一個 base64編碼的密文,瀏覽器隨機(jī)生成,要求服務(wù)端必須返回一個對應(yīng)加密的Sec-WebSocket-Accept應(yīng)答,否則客戶端會拋出Error during WebSocket handshake錯誤,并關(guān)閉連接。- Sec-WebSocket-Version 是告訴服務(wù)器所使用的 Websocket 協(xié)議版本
服務(wù)端收到報文后會返回下列東西,表示已經(jīng)接收到請求,WebSocket建立成功,來自服務(wù)器的握手如下:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
- Upgrade: websocket
- Connection: Upgrade(告訴客戶端即將升級的是WebSocket協(xié)議)
- Sec-WebSocket-Accept的值是服務(wù)端采用與客戶端一致的密鑰計算出來后返回客戶端的
至此,HTTP已經(jīng)完成它所有工作,接下來就是完全按照Websocket協(xié)議進(jìn)行了。
這里值得注意的是Sec-WebSocket-Accept的計算方法:
base64(hsa1(sec-websocket-key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))
如果這個Sec-WebSocket-Accept計算錯誤瀏覽器會提示:Sec-WebSocket-Accept dismatch
如果返回成功,Websocket就會回調(diào)onopen事件。
通過查看WebSocket的原理,與HTTP對比,得出結(jié)論:
HTTP長連接中,每次數(shù)據(jù)交換除了真正的數(shù)據(jù)部分外,服務(wù)器和客戶端還要大量交換HTTP header,信息交換效率很低。Websocket協(xié)議通過第一個請求建立了TCP連接之后,之后交換的數(shù)據(jù)都不需要發(fā)送 HTTP header就能交換數(shù)據(jù),這顯然和原有的HTTP協(xié)議有區(qū)別,所以它需要對服務(wù)器和客戶端都進(jìn)行升級才能實(shí)現(xiàn)(主流瀏覽器都已支持HTML5)。
此外還有 multiplexing、不同的URL可以復(fù)用同一個WebSocket連接等功能。這些都是HTTP長連接不能做到的。
基于以上分析,我們可以看到,websocket能夠提供低延遲,高性能的客戶端與服務(wù)端的雙向數(shù)據(jù)通信。它顛覆了之前web開發(fā)的請求處理響應(yīng)模式,并且提供了一種真正意義上的客戶端請求,服務(wù)器推送數(shù)據(jù)的模式,特別適合實(shí)時數(shù)據(jù)交互應(yīng)用開發(fā)。
對比前面的http的客戶端服務(wù)器的交互圖可以發(fā)現(xiàn)WebSocket方式減少了很多TCP打開和關(guān)閉連接的操作,WebSocket的資源利用率高。
3、websocket的創(chuàng)建和常用的屬性方法
以下 API 用于創(chuàng)建 WebSocket 對象。
var Socket = new WebSocket(url, [protocol] );
以上代碼中的第一個參數(shù) url, 指定連接的 URL。第二個參數(shù) protocol 是可選的,指定了可接受的子協(xié)議。
WebSocket 屬性
以下是 WebSocket 對象的屬性。假定我們使用了以上代碼創(chuàng)建了 Socket 對象:
CONNECTING:值為0,表示正在連接。
OPEN:值為1,表示連接成功,可以通信了。
CLOSING:值為2,表示連接正在關(guān)閉。
CLOSED:值為3,表示連接已經(jīng)關(guān)閉,或者打開連接失敗。
var webSocket = new WebSocket(url); if(webSocket.readyState == webSocket.CONNECTING){ console.log('連接正在打開'); } webSocket.onopen = function () { webSocket.send(consumerId); //可以看到 "連接正在打開"并沒有被打印,說明open對應(yīng)的就是OPEN狀態(tài); if(webSocket.readyState == webSocket.CONNECTING){ console.log('連接正在打開1'); } if(webSocket.readyState == webSocket.OPEN){ console.log('連接已打開'); } sendMsg(); window.weui.alert('已經(jīng)建立連接'); }; //連接關(guān)閉時觸發(fā) webSocket.onclose = function () { if(webSocket.readyState == webSocket.CLOSED){ console.log('連接已關(guān)閉') } window.weui.alert('連接已斷開'); }; //連接 webSocket.onerror = function () { window.weui.alert('連接錯誤,請稍后再試'); };
可以看到,當(dāng)onopen觸發(fā)時,對應(yīng)的就是readyState的OPEN狀態(tài),不包含OPENING;onclose觸發(fā)時,對應(yīng)的就是CLOSED狀態(tài),不包含CLOSING狀態(tài)。
WebSocket 事件
以下是 WebSocket 對象的相關(guān)事件。假定我們使用了以上代碼創(chuàng)建了 Socket 對象:
WebSocket 方法
以下是 WebSocket 對象的相關(guān)方法。假定我們使用了以上代碼創(chuàng)建了 Socket 對象:
用websocket發(fā)送接受二進(jìn)制數(shù)據(jù)
WebSocket
可以通過ArrayBuffer
,發(fā)送或接收二進(jìn)制數(shù)據(jù)。var socket = new WebSocket('ws://127.0.0.1:8081'); socket.binaryType = 'arraybuffer'; // Wait until socket is open socket.addEventListener('open', function (event) { // Send binary data var typedArray = new Uint8Array(4); socket.send(typedArray.buffer); }); // Receive binary data socket.addEventListener('message', function (event) { var arrayBuffer = event.data; // ··· });