最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

搞懂現(xiàn)代Web端即時(shí)通訊技術(shù)一文就夠:WebSocket、socket.io、SSE

2021-09-07 10:55 作者:nickkckckck  | 我要投稿

本文引用自“ 豆米博客”的《JS實(shí)時(shí)通信三把斧》系列文章,有優(yōu)化和改動(dòng)。

1、引言

有關(guān)Web端即時(shí)通訊技術(shù)的文章我已整理過很多篇,閱讀過的讀者可能都很熟悉,早期的Web端即時(shí)通訊方案,受限于Web客戶端的技術(shù)限制,想實(shí)現(xiàn)真正的“即時(shí)”通信,難度相當(dāng)大。

傳統(tǒng)的Web端即時(shí)通訊技術(shù)從短輪詢到長(zhǎng)連詢,再到Comet技術(shù),在如此原始的HTML標(biāo)準(zhǔn)之下,為了實(shí)現(xiàn)所謂的“即時(shí)”通信,技術(shù)上可謂絞盡腦汁,極盡所能。

自從HTML5標(biāo)準(zhǔn)發(fā)布之后,WebSocket這類技術(shù)橫空出世,實(shí)現(xiàn)Web端即時(shí)通訊技術(shù)的便利性大大提前,以往想都不敢想的真正全雙工實(shí)時(shí)通信,如此早已成為可能。

本文將專門介紹WebSocket、socket.io、SSE這幾種現(xiàn)代的Web端即時(shí)通訊技術(shù),從適用場(chǎng)景到技術(shù)原理,通俗又不失深度的文字,特別適合對(duì)Web端即時(shí)通訊技術(shù)有一定了解,且想深入學(xué)習(xí)WebSocket等現(xiàn)代Web端“實(shí)時(shí)”通信技術(shù),卻又不想花時(shí)間去深讀枯燥的IETF技術(shù)手冊(cè)的讀者

?

學(xué)習(xí)交流:

- 即時(shí)通訊/推送技術(shù)開發(fā)交流5群:215477170?[推薦]

- 移動(dòng)端IM開發(fā)入門文章:《新手入門一篇就夠:從零開發(fā)移動(dòng)端IM》

- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK

(本文同步發(fā)布于:http://www.52im.net/thread-3695-1-1.html)

2、本文作者

“豆米”:現(xiàn)居杭州,熱愛前端,熱愛互聯(lián)網(wǎng),豆米是“洋芋(土豆-豆)”和“米喳(米)”的簡(jiǎn)稱。

作者博客:https://blog.5udou.cn/

作者Github:https://github.com/linxiaowu66/

3、知識(shí)預(yù)備

如果你對(duì)Web端即時(shí)通訊技術(shù)的前世今生不曾了解,建議先讀以下文章:

  1. 《新手入門貼:史上最全Web端即時(shí)通訊技術(shù)原理詳解》

  2. 《Web端即時(shí)通訊技術(shù)盤點(diǎn):短輪詢、Comet、Websocket、SSE》

  3. 《詳解Web端通信方式的演進(jìn):從Ajax、JSONP 到 SSE、Websocket》

  4. 《網(wǎng)頁(yè)端IM通信技術(shù)快速入門:短輪詢、長(zhǎng)輪詢、SSE、WebSocket》

如果你對(duì)本文將要介紹的技術(shù)已有了解,建議進(jìn)行專項(xiàng)學(xué)習(xí),以便深入掌握:

  1. 《Comet技術(shù)詳解:基于HTTP長(zhǎng)連接的Web端實(shí)時(shí)通信技術(shù)》

  2. 《SSE技術(shù)詳解:一種全新的HTML5服務(wù)器推送事件技術(shù)》

  3. 《WebSocket詳解(三):深入WebSocket通信協(xié)議細(xì)節(jié)》

  4. 《理論聯(lián)系實(shí)際:從零理解WebSocket的通信原理、協(xié)議格式、安全性》

  5. 《WebSocket從入門到精通,半小時(shí)就夠!》

4、WebSocket

在這里不打算詳細(xì)介紹整個(gè)WebSocket協(xié)議的內(nèi)容,根據(jù)我本人以前協(xié)議的學(xué)習(xí)思路,我挑重點(diǎn)使用問答方式來介紹該協(xié)議,這樣讀起來就不那么枯燥。

4.1 基本情況

協(xié)議運(yùn)行在OSI的哪層?

應(yīng)用層,WebSocket協(xié)議是一個(gè)獨(dú)立的基于TCP的協(xié)議。 它與HTTP唯一的關(guān)系是它的握手是由HTTP服務(wù)器解釋為一個(gè)Upgrade請(qǐng)求。

協(xié)議運(yùn)行的標(biāo)準(zhǔn)端口號(hào)是多少?

默認(rèn)情況下,WebSocket協(xié)議使用端口80用于常規(guī)的WebSocket連接、端口443用于WebSocket連接的在傳輸層安全(TLS)RFC2818之上的隧道化口。

4.2 協(xié)議是如何工作的?

協(xié)議的工作流程可以參考下圖:

其中幀的一些重要字段需要解釋一下:

  • 1)Upgrade:`upgrade`是HTTP1.1中用于定義轉(zhuǎn)換協(xié)議的`header`域。它表示,如果服務(wù)器支持的話,客戶端希望使用現(xiàn)有的「網(wǎng)絡(luò)層」已經(jīng)建立好的這個(gè)「連接(此處是 TCP 連接)」,切換到另外一個(gè)「應(yīng)用層」(此處是 WebSocket)協(xié)議;

  • 2)Connection:`Upgrade`固定字段。Connection還有其他字段,可以自己給自己科普一下;

  • 3)Sec-WebSocket-Key:用來發(fā)送給服務(wù)器使用(服務(wù)器會(huì)使用此字段組裝成另一個(gè)key值放在握手返回信息里發(fā)送客戶端);

  • 4)Sec-WebSocket-Protocol:標(biāo)識(shí)了客戶端支持的子協(xié)議的列表;

  • 5)Sec-WebSocket-Version:標(biāo)識(shí)了客戶端支持的WS協(xié)議的版本列表,如果服務(wù)器不支持這個(gè)版本,必須回應(yīng)自己支持的版本;

  • 6)Origin:作安全使用,防止跨站攻擊,瀏覽器一般會(huì)使用這個(gè)來標(biāo)識(shí)原始域;

  • 7)Sec-WebSocket-Accept:服務(wù)器響應(yīng),包含Sec-WebSocket-Key 的簽名值,證明它支持請(qǐng)求的協(xié)議版本。

關(guān)于Sec-WebSocket-Key和Sec-WebSocket-Accept的計(jì)算是這樣的:

所有兼容RFC 6455 的WebSocket 服務(wù)器都使用相同的算法計(jì)算客戶端挑戰(zhàn)的答案:將Sec-WebSocket-Key 的內(nèi)容與標(biāo)準(zhǔn)定義的唯一GUID字符(258EAFA5-E914-47DA-95CA-C5AB0DC85B11)串拼接起來,計(jì)算出SHA1散列值,結(jié)果是一個(gè)base-64編碼的字符串,把這個(gè)字符串發(fā)給客戶端即可。

用代碼就是實(shí)現(xiàn)如下:

const key = crypto.createHash('sha1')

??????.update(req.headers['sec-websocket-key'] + constants.GUID, 'binary')

??????.digest('base64')

至于為什么需要這么一個(gè)步驟,可以參考《理論聯(lián)系實(shí)際:從零理解WebSocket的通信原理、協(xié)議格式、安全性》一文。

引用如下:

Sec-WebSocket-Key/Sec-WebSocket-Accept在主要作用在于提供基礎(chǔ)的防護(hù),減少惡意連接、意外連接。

作用大致歸納如下:

  • 1)避免服務(wù)端收到非法的websocket連接(比如http客戶端不小心請(qǐng)求連接websocket服務(wù),此時(shí)服務(wù)端可以直接拒絕連接);

  • 2)確保服務(wù)端理解websocket連接。因?yàn)閣s握手階段采用的是http協(xié)議,因此可能ws連接是被一個(gè)http服務(wù)器處理并返回的,此時(shí)客戶端可以通過Sec-WebSocket-Key來確保服務(wù)端認(rèn)識(shí)ws協(xié)議。(并非百分百保險(xiǎn),比如總是存在那么些無聊的http服務(wù)器,光處理Sec-WebSocket-Key,但并沒有實(shí)現(xiàn)ws協(xié)議。。。);

  • 3)用瀏覽器里發(fā)起ajax請(qǐng)求,設(shè)置header時(shí),Sec-WebSocket-Key以及其他相關(guān)的header是被禁止的。這樣可以避免客戶端發(fā)送ajax請(qǐng)求時(shí),意外請(qǐng)求協(xié)議升級(jí)(websocket upgrade);

  • 4)可以防止反向代理(不理解ws協(xié)議)返回錯(cuò)誤的數(shù)據(jù)。比如反向代理前后收到兩次ws連接的升級(jí)請(qǐng)求,反向代理把第一次請(qǐng)求的返回給cache住,然后第二次請(qǐng)求到來時(shí)直接把cache住的請(qǐng)求給返回(無意義的返回);

  • 5)Sec-WebSocket-Key主要目的并不是確保數(shù)據(jù)的安全性,因?yàn)镾ec-WebSocket-Key、Sec-WebSocket-Accept的轉(zhuǎn)換計(jì)算公式是公開的,而且非常簡(jiǎn)單,最主要的作用是預(yù)防一些常見的意外情況(非故意的)。

強(qiáng)調(diào):Sec-WebSocket-Key/Sec-WebSocket-Accept?的換算,只能帶來基本的保障,但連接是否安全、數(shù)據(jù)是否安全、客戶端/服務(wù)端是否合法的 ws客戶端、ws服務(wù)端,其實(shí)并沒有實(shí)際性的保證。

4.3 協(xié)議傳輸?shù)膸袷绞鞘裁?

幀格式定義的格式如下:

各個(gè)字段的解釋如下:

  • 1)FIN: 1bit,用來表明這是一個(gè)消息的最后的消息片斷,當(dāng)然第一個(gè)消息片斷也可能是最后的一個(gè)消息片斷;

  • 2)RSV1,RSV2,RSV3: 分別都是1位,如果雙方之間沒有約定自定義協(xié)議,那么這幾位的值都必須為0,否則必須斷掉WebSocket連接。在ws中就用到了RSV1來表示是否消息壓縮了的;

  • 3)opcode:4 bit,表示被傳輸幀的類型:

  • - %x0 表示連續(xù)消息片斷;

  • -??%x1 表示文本消息片斷;

  • -??%x2 表未二進(jìn)制消息片斷;

  • -??%x3-7 為將來的非控制消息片斷保留的操作碼;

  • -??%x8 表示連接關(guān)閉;

  • -??%x9 表示心跳檢查的ping;

  • -??%xA 表示心跳檢查的pong;

  • -??%xB-F 為將來的控制消息片斷的保留操作碼。

  • 4)Mask: 1 bit。定義傳輸?shù)臄?shù)據(jù)是否有加掩碼,如果設(shè)置為1,掩碼鍵必須放在masking-key區(qū)域,客戶端發(fā)送給服務(wù)端的所有消息,此位都是1;

  • 5)Payload length:傳輸數(shù)據(jù)的長(zhǎng)度,以字節(jié)的形式表示:7位、7+16位、或者7+64位。如果這個(gè)值以字節(jié)表示是0-125這個(gè)范圍,那這個(gè)值就表示傳輸數(shù)據(jù)的長(zhǎng)度;如果這個(gè)值是126,則隨后的兩個(gè)字節(jié)表示的是一個(gè)16進(jìn)制無符號(hào)數(shù),用來表示傳輸數(shù)據(jù)的長(zhǎng)度;如果這個(gè)值是127,則隨后的是8個(gè)字節(jié)表示的一個(gè)64位無符合數(shù),這個(gè)數(shù)用來表示傳輸數(shù)據(jù)的長(zhǎng)度。多字節(jié)長(zhǎng)度的數(shù)量是以網(wǎng)絡(luò)字節(jié)的順序表示。負(fù)載數(shù)據(jù)的長(zhǎng)度為擴(kuò)展數(shù)據(jù)及應(yīng)用數(shù)據(jù)之和,擴(kuò)展數(shù)據(jù)的長(zhǎng)度可能為0,因而此時(shí)負(fù)載數(shù)據(jù)的長(zhǎng)度就為應(yīng)用數(shù)據(jù)的長(zhǎng)度;

  • 6)Masking-key:0或4個(gè)字節(jié),客戶端發(fā)送給服務(wù)端的數(shù)據(jù),都是通過內(nèi)嵌的一個(gè)32位值作為掩碼的;掩碼鍵只有在掩碼位設(shè)置為1的時(shí)候存在;

  • 7)Extension data: x位,如果客戶端與服務(wù)端之間沒有特殊約定,那么擴(kuò)展數(shù)據(jù)的長(zhǎng)度始終為0,任何的擴(kuò)展都必須指定擴(kuò)展數(shù)據(jù)的長(zhǎng)度,或者長(zhǎng)度的計(jì)算方式,以及在握手時(shí)如何確定正確的握手方式。如果存在擴(kuò)展數(shù)據(jù),則擴(kuò)展數(shù)據(jù)就會(huì)包括在負(fù)載數(shù)據(jù)的長(zhǎng)度之內(nèi);

  • 8)Application data: y位,任意的應(yīng)用數(shù)據(jù),放在擴(kuò)展數(shù)據(jù)之后,應(yīng)用數(shù)據(jù)的長(zhǎng)度=負(fù)載數(shù)據(jù)的長(zhǎng)度-擴(kuò)展數(shù)據(jù)的長(zhǎng)度;

  • 9)Payload data: (x+y)位,負(fù)載數(shù)據(jù)為擴(kuò)展數(shù)據(jù)及應(yīng)用數(shù)據(jù)長(zhǎng)度之和;

更多細(xì)節(jié)請(qǐng)參考RFC6455-數(shù)據(jù)幀,這里不作贅述。

針對(duì)上面的各個(gè)字段的介紹,有一個(gè)Mask的需要說一下。

掩碼鍵(Masking-key)是由客戶端挑選出來的32位的隨機(jī)數(shù)。掩碼操作不會(huì)影響數(shù)據(jù)載荷的長(zhǎng)度。

掩碼、反掩碼操作都采用如下算法。

首先,假設(shè):

  • 1)original-octet-i:為原始數(shù)據(jù)的第i字節(jié);

  • 2)transformed-octet-i:為轉(zhuǎn)換后的數(shù)據(jù)的第i字節(jié);

  • 3)j:為i mod 4的結(jié)果;

  • 4)masking-key-octet-j:為mask key第j字節(jié)。

算法描述為:?original-octet-i 與 masking-key-octet-j 異或后,得到 transformed-octet-i。

即:?j = i MOD 4 transformed-octet-i = original-octet-i XOR masking-key-octet-j

用代碼實(shí)現(xiàn):

const mask = (source, mask, output, offset, length) => {

??for(vari = 0; i < length; i++) {

????output[offset + i] = source[i ] ^ mask[i & 3];

??}

};

解掩碼是反過來的操作:

const unmask = (buffer, mask) => {

??// Required until [url=https://github.com/nodejs/node/issues/9006]https://github.com/nodejs/node/issues/9006[/url] is resolved.

??const length = buffer.length;

??for(vari = 0; i < length; i++) {

????buffer[i ] ^= mask[i & 3];

??}

};

同樣的為什么需要掩碼操作,也可以參考之前的那篇文章:《理論聯(lián)系實(shí)際:從零理解WebSocket的通信原理、協(xié)議格式、安全性》,完整的我就不列舉了。

需要注意的重點(diǎn),我引用一下:

WebSocket協(xié)議中,數(shù)據(jù)掩碼的作用是增強(qiáng)協(xié)議的安全性。但數(shù)據(jù)掩碼并不是為了保護(hù)數(shù)據(jù)本身,因?yàn)樗惴ū旧硎枪_的,運(yùn)算也不復(fù)雜。除了加密通道本身,似乎沒有太多有效的保護(hù)通信安全的辦法。

那么為什么還要引入掩碼計(jì)算呢,除了增加計(jì)算機(jī)器的運(yùn)算量外似乎并沒有太多的收益(這也是不少同學(xué)疑惑的點(diǎn))。

答案還是兩個(gè)字: 安全。但并不是為了防止數(shù)據(jù)泄密,而是為了防止早期版本的協(xié)議中存在的代理緩存污染攻擊(proxy cache poisoning attacks)等問題。

5、socket.io

5.1 本節(jié)引言

介紹完上一節(jié)WebSocket協(xié)議,我們把視線轉(zhuǎn)移到現(xiàn)代Web端即時(shí)通訊技術(shù)的第二個(gè)利器:socket.io。

估計(jì)有讀者就會(huì)問,WebSocket和socket.io有啥區(qū)別啊?

在了解socket.io之前,我們先聊聊傳統(tǒng)Web端即時(shí)通訊“長(zhǎng)連接”技術(shù)的實(shí)現(xiàn)背景。

5.2 傳統(tǒng)Web長(zhǎng)連接的技術(shù)實(shí)現(xiàn)背景

在現(xiàn)實(shí)的Web端產(chǎn)品中,并不是所有的Web客戶端都支持長(zhǎng)連接的,或者換句話說,在WebSocket協(xié)議出來之前,是三種方式去實(shí)現(xiàn)WebSocket類似的功能的。

這三種方式是:

  • 1)Flash:使用Flash是一種簡(jiǎn)單的方法。不過很明顯的缺點(diǎn)就是Flash并不會(huì)安裝在所有客戶端上,比如iPhone/iPad。

  • 2)Long-Polling:也就是眾所周之的“長(zhǎng)輪詢”,在過去,這是一種有效的技術(shù),但并沒有對(duì)消息發(fā)送進(jìn)行優(yōu)化。雖然我不會(huì)把AJAX長(zhǎng)輪詢當(dāng)做一種hack技術(shù),但它確實(shí)不是一個(gè)最優(yōu)方法;

  • 3)Comet:在過去,這被稱為Web端的“服務(wù)器推”技術(shù),相對(duì)于傳統(tǒng)的 Web 應(yīng)用, 開發(fā) Comet 應(yīng)用具有一定的挑戰(zhàn)性,具體請(qǐng)見《Comet技術(shù)詳解:基于HTTP長(zhǎng)連接的Web端實(shí)時(shí)通信技術(shù)》。

那么如果單純地使用WebSocket的話,那些不支持的客戶端怎么辦呢?難道直接放棄掉?

當(dāng)然不是。Guillermo Rauch大神寫了socket.io這個(gè)庫(kù),對(duì)WebSocket進(jìn)行封裝,從而讓長(zhǎng)連接滿足所有的場(chǎng)景,不過當(dāng)然得配合使用對(duì)應(yīng)的客戶端代碼。

socket.io將會(huì)使用特性檢測(cè)的方式來決定以websocket/ajax長(zhǎng)輪詢/flash等方式建立連接。

那么socket.io是如何做到這些的呢?

我們帶著以下幾個(gè)問題去學(xué)習(xí):

  • 1)socket.io到底有什么新特性?

  • 2)socket.io是怎么實(shí)現(xiàn)特性檢測(cè)的?

  • 3)socket.io有哪些坑呢?

  • 4)socket.io的實(shí)際應(yīng)用是怎樣的,需要注意些什么?

如果有童鞋對(duì)上述問題已經(jīng)清楚,想必就沒有往下讀的必要了。

5.3 socket.io的介紹

通過前面章節(jié),讀者們都知道了WebSocket的功能,那么socket.io相對(duì)于WebSocket,在此基礎(chǔ)上封裝了一些什么新東西呢?

socket.io其實(shí)是有一套封裝了websocket的協(xié)議,叫做engine.io協(xié)議,在此協(xié)議上實(shí)現(xiàn)了一套底層雙向通信的引擎Engine.io。

而socket.io則是建立在engine.io上的一個(gè)應(yīng)用層框架而已。所以我們研究的重點(diǎn)便是engine.io協(xié)議。

在socket.io的README中提到了其實(shí)現(xiàn)的一些新特性(回答了問題一):

  • 1)可靠性:連接依然可以建立即使應(yīng)用環(huán)境存在: 代理或者負(fù)載均衡器 個(gè)人防火墻或者反病毒軟件;

  • 2)支持自動(dòng)連接: 除非特別指定,否則一個(gè)斷開的客戶端會(huì)一直重連服務(wù)器直到服務(wù)器恢復(fù)可用狀態(tài);

  • 3)斷開連接檢測(cè):在Engine.io層實(shí)現(xiàn)了一個(gè)心跳機(jī)制,這樣允許客戶端和服務(wù)器知道什么時(shí)候其中的一方不能響應(yīng)。該功能是通過設(shè)置在服務(wù)端和客戶端的定時(shí)器實(shí)現(xiàn)的,在連接握手的時(shí)候,服務(wù)器會(huì)主動(dòng)告知客戶端心跳的間隔時(shí)間以及超時(shí)時(shí)間;

  • 4)二進(jìn)制的支持:任何序列化的數(shù)據(jù)結(jié)構(gòu)都可以用來發(fā)送;

  • 5)跨瀏覽器的支持:該庫(kù)甚至支持到IE8;

  • 6)支持復(fù)用:為了在應(yīng)用程序中將創(chuàng)建的關(guān)注點(diǎn)隔離開來,Socket.io允許你創(chuàng)建多個(gè)namespace,這些namespace擁有單獨(dú)的通信通道,但將共享相同的底層連接;

  • 7)支持Room:在每一個(gè)namespace下,你可以定義任意數(shù)量的通道,我們稱之為"房間",你可以加入或者離開房間,甚至廣播消息到指定的房間。

注意:Socket.IO不是WebSocket的實(shí)現(xiàn),雖然 Socket.IO確實(shí)在可能的情況下會(huì)去使用WebSocket作為一個(gè)transport,但是它添加了很多元數(shù)據(jù)到每一個(gè)報(bào)文中:報(bào)文的類型以及namespace和ack Id。這也是為什么標(biāo)準(zhǔn)WebSocket客戶端不能夠成功連接上 Socket.IO 服務(wù)器,同樣一個(gè) Socket.IO 客戶端也連接不上標(biāo)準(zhǔn)WebSocket服務(wù)器的原因。

5.4 engine.io協(xié)議介紹

完整的engine.io協(xié)議的握手過程如下圖:

當(dāng)前engine.io協(xié)議的版本是3,我們根據(jù)上圖來大致介紹一下engine.io協(xié)議。

5.4.1)engine.io協(xié)議請(qǐng)求字段:

我們看到的是請(qǐng)求的url和WebSocket不大一樣,解釋一下:

  • 1)EIO=3: 表示的是使用的是Engine.io協(xié)議版本3;

  • 2)transport=polling/websocket: 表示使用的長(zhǎng)連接方式是輪詢還是WebSocket;

  • 3)t=xxxxx: 代碼中使用yeast根據(jù)時(shí)間戳生成一個(gè)唯一的字符串;

  • 4)sid=xxxx: 客戶端和服務(wù)器建立連接之后獲取到的session id,客戶端拿到之后必須在每次請(qǐng)求中追加這個(gè)字段。

除了上述的3個(gè)字段,協(xié)議還描述了下面幾個(gè)字段:

  • 1)j: 如果transport是polling,但是要求有一個(gè)JSONP的響應(yīng),那么j就應(yīng)該設(shè)置為JSONP響應(yīng)的索引值;

  • 2)b64: 如果客戶端不支持XHR,那么客戶端應(yīng)該設(shè)置b64=1傳給服務(wù)器,告知服務(wù)器所有的二進(jìn)制數(shù)據(jù)應(yīng)該以base64編碼后再發(fā)送。

另外engine.io默認(rèn)的path是?/engine.io,socket.io在初始化的時(shí)候設(shè)置為了?/socket.io,所以大家看到的path就都是?/socket.io?了:

function Server(srv, opts){

??if(!(this instanceof Server)) return new Server(srv, opts);

??if('object'== typeof srv && srv instanceof Object && !srv.listen) {

????opts = srv;

????srv = null;

??}

??opts = opts || {};

??this.nsps = {};

??this.parentNsps = new Map();

??this.path(opts.path || '/socket.io');

5.4.2)數(shù)據(jù)包編碼要求:

engine.io協(xié)議的數(shù)據(jù)包編碼有自己的一套格式,在協(xié)議介紹上engine.io-protocol,定義了兩種編碼類型: packet和payload。

一個(gè)編碼過的packet是下面這種格式:

<packettype id>[<data>]

然后協(xié)議定義了下面幾種packet type(采用數(shù)字進(jìn)行標(biāo)識(shí)):

  • 1)0(open): 當(dāng)開始一個(gè)新的transport的時(shí)候,服務(wù)端會(huì)發(fā)送該類型的packet;

  • 2)1(close): 請(qǐng)求關(guān)閉這個(gè)transport但是不要自己關(guān)閉關(guān)閉連接;

  • 3)2(ping): 由客戶端發(fā)送的ping包,服務(wù)端必須回應(yīng)一個(gè)包含相同數(shù)據(jù)的pong包;

  • 4)3(pong): 響應(yīng)ping包,服務(wù)端發(fā)送;

  • 5)4(message): 實(shí)際消息,在客戶端和服務(wù)端都可以監(jiān)聽message事件獲取消息內(nèi)容;

  • 6)5(upgrade): 在engine.io切換transport之前,它會(huì)用來測(cè)試服務(wù)端和客戶端是否在該transport上通信。如果測(cè)試成功,客戶端會(huì)發(fā)送一個(gè)upgrade包去讓服務(wù)器刷新它的緩存并切換到新的transport;

  • 7)6(noop): 主要用來強(qiáng)制一個(gè)輪詢循環(huán)當(dāng)收到一個(gè)WebSocket連接的時(shí)候。

那payload也有對(duì)應(yīng)的格式要求:

  • 1)如果當(dāng)只有發(fā)送string并且不支持XHR的時(shí)候,其編碼格式是::[:[...]];

  • 2)當(dāng)不支持XHR2并且發(fā)送二進(jìn)制數(shù)據(jù),但是使用base64編碼字符串的時(shí)候,其編碼格式是::b[...];

  • 3)當(dāng)支持XHR2的時(shí)候,所有的數(shù)據(jù)都被編碼成二進(jìn)制,格式是:<0 for string data, 1 for binary data>[...];

  • 4)如果發(fā)送的內(nèi)容混雜著UTF-8的字符和二進(jìn)制數(shù)據(jù),字符串的每個(gè)字符被寫成一個(gè)字符編碼,用1個(gè)字節(jié)表示。

注意:payload的編碼要求不適用于WebSocket的通信。

針對(duì)上面的編碼要求,我們隨便舉個(gè)例子.

之前在第一條polling請(qǐng)求的時(shí)候,服務(wù)端編碼發(fā)送了這個(gè)數(shù)據(jù):

97:0{"sid":"Peed250dk55pprwgAAAA","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":60000}2:40

根據(jù)上面的知識(shí),我們知道第一次服務(wù)端會(huì)發(fā)送一個(gè)open的數(shù)據(jù)包。

所以組裝出來的packet是:

0

然后服務(wù)端會(huì)告知客戶端去嘗試升級(jí)到websocket,并且告知對(duì)應(yīng)的sid。

于是整合后便是:

0{"sid":"Peed250dk55pprwgAAAA","upgrades":"websocket","pingInterval":25000,"pingTimeout":60000}

接著根據(jù)payload的編碼格式,因?yàn)槭莝tring,且長(zhǎng)度是97個(gè)字節(jié)。

所以是:

97:0{"sid":"Peed250dk55pprwgAAAA","upgrades":"websocket","pingInterval":25000,"pingTimeout":60000}

接著第二部分?jǐn)?shù)據(jù)是message包類型,并且數(shù)據(jù)是0,所以是40,長(zhǎng)度為2字節(jié),所以是2:40,最后就拼成剛才大家看到的結(jié)果。

注意:

ping/pong的間隔時(shí)間是服務(wù)端告知客戶端的:"pingInterval":25000,"pingTimeout":60000,也就是說心跳時(shí)間默認(rèn)是25秒,并且等待pong響應(yīng)的時(shí)間默認(rèn)是60s。

5.5 升級(jí)協(xié)議的必備過程

協(xié)議定義了transport升級(jí)到websocket需要經(jīng)歷一個(gè)必須的過程。

如下圖:

WebSocket的測(cè)試開始于發(fā)送probe,如果服務(wù)器也響應(yīng)probe的話,客戶端就必須發(fā)送一個(gè)upgrade包。

為了確保不會(huì)丟包,只有在當(dāng)前transport的所有buffer被刷新并且transport被認(rèn)為paused的時(shí)候才可以發(fā)送upgrade包。服務(wù)端收到upgrade包的時(shí)候,服務(wù)端必須假設(shè)這是一個(gè)新的通道并發(fā)送所有已存的緩存到這個(gè)通道上

在Chrome上的效果如下:

5.6 engine.io的代碼實(shí)現(xiàn)

熟悉了engine.io協(xié)議之后,我們看看代碼是怎么實(shí)現(xiàn)主流程的。

客戶端的engine.io的主要實(shí)現(xiàn)流程我們?cè)谏厦嫖淖纸榻B了。

結(jié)合代碼engine.io,畫了這么一個(gè)客戶端流程圖:

服務(wù)端的代碼和客戶端非常相似,其實(shí)現(xiàn)流程圖如下:

6、SSE

6.1 本節(jié)引言

本文前兩節(jié)分析了WebSocket和socket.io,現(xiàn)在我們來看看SSE。

很多人也許好奇,有了WebSocket這種實(shí)時(shí)通信,為什么還需要SSE呢?

答案其實(shí)很簡(jiǎn)單:那就是SSE其實(shí)是單向通信,而WebSocket是雙向通信。

比如:在股票行情、新聞推送的這種只需要服務(wù)器發(fā)送消息給客戶端場(chǎng)景中,使用SSE可能更加合適。

另外:SSE是使用HTTP傳輸?shù)?,這意味著我們不需要一個(gè)特殊的協(xié)議或者額外的實(shí)現(xiàn)就可以使用。而WebSocket要求全雙工連接和一個(gè)新的WebSocket服務(wù)器去處理。加上SSE在設(shè)計(jì)的時(shí)候就有一些WebSocket沒有的特性,比如自動(dòng)重連接、event IDs、以及發(fā)送隨機(jī)事件的能力,所以各有各的特長(zhǎng),我們需要根據(jù)實(shí)際應(yīng)用場(chǎng)景,去選擇不同的應(yīng)用方案。

6.2 SSE介紹

SSE的簡(jiǎn)單模型是:一個(gè)客戶端去從服務(wù)器端訂閱一條“流”,之后服務(wù)端可以發(fā)送消息給客戶端直到服務(wù)端或者客戶端關(guān)閉該“流”,所以SSE全稱叫“server-sent-event”。

相比以前的輪詢,SSE可以為B2C帶來更高的效率。

有一張圖片畫出了二者的區(qū)別:

6.3 SSE數(shù)據(jù)幀的格式

SSE必須編碼成utf-8的格式,消息的每個(gè)字段使用"\n"來做分割,并且需要下面4個(gè)規(guī)范定義好的字段。

這4個(gè)字段是:

  • 1)Event: 事件類型;

  • 2)Data: 發(fā)送的數(shù)據(jù);

  • 3)ID: 每一條事件流的ID;

  • 4)Retry: 告知瀏覽器在所有的連接丟失之后重新開啟新的連接等待的時(shí)間,在自動(dòng)重新連接的過程中,之前收到的最后一個(gè)事件流ID會(huì)被發(fā)送到服務(wù)端。

下圖是通過wireshark抓包得到的數(shù)據(jù)包的原始格式:

6.4 SSE通信過程

SSE的通信過程比較簡(jiǎn)單,底層的一些實(shí)現(xiàn)都被瀏覽器給封裝好了,包括數(shù)據(jù)的處理。

大致流程如下:

在瀏覽器中截圖如下:

攜帶的數(shù)據(jù)是JSON格式的,瀏覽器都幫你整合成為一個(gè)Object:

在wireshark中,其通信流程如下。

發(fā)送請(qǐng)求:

得到響應(yīng):

在開始推送信息流之前,服務(wù)器還會(huì)發(fā)送一個(gè)客戶端會(huì)忽略掉的包,這個(gè)具體原因不清楚:

斷開連接后的重傳:

?

6.5 SSE的簡(jiǎn)單使用示例

瀏覽器端的使用:

const es = new EventSource('/sse')

服務(wù)端的使用:

const sseStream = new SseStream(req)

sseStream.pipe(res)

sseStream.write({

??id: sendCount,

??event: 'server-time',

??retry: 20000, // 告訴客戶端,如果斷開連接后,20秒后再重試連接

??data: {ts: newDate().toTimeString(), count: sendCount++}

})

更多API使用和demo介紹分別參考:SSE API、demo代碼。

6.6 兼容性及缺點(diǎn)

兼容性:

▲ 上圖來自?https://caniuse.com/?search=Server-Sent-Events

缺點(diǎn):

  • 1)因?yàn)槭欠?wù)器 -> 客戶端的,所以它不能處理客戶端請(qǐng)求流;

  • 2)因?yàn)槭敲鞔_指定用于傳輸U(kuò)TF-8數(shù)據(jù)的,所以對(duì)于傳輸二進(jìn)制流是低效率的,即使你轉(zhuǎn)為base64的話,反而增加帶寬的負(fù)載,得不償失。

7、參考資料

[1]?WebSocket API文檔

[2]?SSE API文檔

[3]?新手入門貼:史上最全Web端即時(shí)通訊技術(shù)原理詳解

[4]?Web端即時(shí)通訊技術(shù)盤點(diǎn):短輪詢、Comet、Websocket、SSE

[5]?SSE技術(shù)詳解:一種全新的HTML5服務(wù)器推送事件技術(shù)

[6]?Comet技術(shù)詳解:基于HTTP長(zhǎng)連接的Web端實(shí)時(shí)通信技術(shù)

[7]?新手快速入門:WebSocket簡(jiǎn)明教程

[8]?WebSocket詳解(三):深入WebSocket通信協(xié)議細(xì)節(jié)

[9]?WebSocket詳解(四):刨根問底HTTP與WebSocket的關(guān)系(上篇)

[10]?WebSocket詳解(五):刨根問底HTTP與WebSocket的關(guān)系(下篇)

[11]?使用WebSocket和SSE技術(shù)實(shí)現(xiàn)Web端消息推送

[12]?詳解Web端通信方式的演進(jìn):從Ajax、JSONP 到 SSE、Websocket

[13]?MobileIMSDK-Web的網(wǎng)絡(luò)層框架為何使用的是Socket.io而不是Netty?

[14]?理論聯(lián)系實(shí)際:從零理解WebSocket的通信原理、協(xié)議格式、安全性

[15]?WebSocket從入門到精通,半小時(shí)就夠!

[16]?WebSocket硬核入門:200行代碼,教你徒手?jǐn)]一個(gè)WebSocket服務(wù)器

[17]?網(wǎng)頁(yè)端IM通信技術(shù)快速入門:短輪詢、長(zhǎng)輪詢、SSE、WebSocket

本文已同步發(fā)布于“即時(shí)通訊技術(shù)圈”公眾號(hào)。

同步發(fā)布鏈接是:http://www.52im.net/thread-3695-1-1.html


搞懂現(xiàn)代Web端即時(shí)通訊技術(shù)一文就夠:WebSocket、socket.io、SSE的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
巴青县| 乐亭县| 济宁市| 河南省| 温州市| 赣州市| 腾冲县| 宁乡县| 北宁市| 元阳县| 麻江县| 峨边| 邳州市| 黄大仙区| 军事| 凤翔县| 普兰店市| 宜都市| 昌都县| 琼海市| 金昌市| 云浮市| 达日县| 澄迈县| 乡城县| 贺兰县| 昆山市| 闻喜县| 金寨县| 海城市| 邵武市| 平武县| 平安县| 涞源县| 赤城县| 兴隆县| 北碚区| 黄龙县| 手游| 宾川县| 莆田市|