websocket 協(xié)議解析 [RFC6455]

近期工作忙碌,為了趕SegmentFault for Android 4.0版,到了發(fā)瘋的程度。
我來(lái)匯報(bào)一個(gè)進(jìn)度,已經(jīng)實(shí)現(xiàn)基于websocket的私信系統(tǒng)了,多虧了70大大不懈的努力,在不久的將來(lái),我們使用web給手機(jī)發(fā)送私信的愿望很快就可以達(dá)成了。

那么在這之余,因?yàn)閭€(gè)人對(duì)各個(gè)通信協(xié)議都有頗有興趣,便順便去看了下ietf寫的關(guān)于websocket的文章,原文鏈接在這:

https://tools.ietf.org/html/rfc6455

當(dāng)然如果你對(duì)實(shí)現(xiàn)websocket協(xié)議并沒(méi)什么興趣的話,本文可以直接略過(guò),因?yàn)槟愦蟾胖涝趺从镁涂梢粤? =

概念與背景

websocket 目前仍未有標(biāo)準(zhǔn)化的文檔,所以目前我的理解是基于RFC6455草案。

websocket的誕生場(chǎng)景是因?yàn)槲覀冊(cè)跒g覽器上缺少一種與服務(wù)器保持長(zhǎng)連接的標(biāo)準(zhǔn)化的技術(shù),因?yàn)?code>HTTP 1.1(以下簡(jiǎn)稱為HTTP)協(xié)議只是一個(gè)標(biāo)準(zhǔn)的無(wú)狀態(tài)協(xié)議,并不存在除了requestresponse生命周期之外的通信場(chǎng)景。如果我們需要實(shí)現(xiàn)在線聊天的功能的話,那么基于HTTP,我們只能使用丑陋的long pollingajax 輪詢等非正常的技術(shù)實(shí)現(xiàn)我們需要的功能。這些方案雖然解決了我們的使用場(chǎng)景,但并不是最好的方案,我們的HTTP服務(wù)器軟件并不是設(shè)計(jì)來(lái)保持長(zhǎng)連接的。

基于以上場(chǎng)景,websocket誕生了。

  1. 它是一種真正的長(zhǎng)連接,能讓服務(wù)端和客戶端進(jìn)行持久的通信,輕松的實(shí)現(xiàn)服務(wù)器推送技術(shù)。
  2. 它和HTTP并沒(méi)有特別多的關(guān)系,但是它的握手 (handshake) 是利用HTTP來(lái)完成。
  3. 它的本質(zhì)是基于TCP的連接,可以說(shuō)和HTTP屬于平級(jí)的應(yīng)用層協(xié)議。

草案闡述了websocket的設(shè)計(jì)目標(biāo),除了建立起基于TCP的一層連接意外,還有以下幾個(gè)特點(diǎn)

  • 為瀏覽器增加一個(gè)基于域名的安全模型(也就是跨域安全模型)
  • 增加一個(gè)基于域名和地址的機(jī)制,用來(lái)支持在一個(gè)IP地址上的一端口多域名的多服務(wù)模型(類似nginx在同一個(gè)ip上使用不同的域名來(lái)路由到不同的服務(wù)上)
  • 在流式的TCP建立一個(gè)幀數(shù)據(jù)包模型,并且沒(méi)有長(zhǎng)度限制
  • 增加了一個(gè)額外的關(guān)閉連接握手,給代理和其他中間件使用。

連接時(shí)握手

websocket的握手實(shí)際上就是給服務(wù)器發(fā)送一個(gè)GET請(qǐng)求,里面帶上指定的header即可。

request例子如下

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

其中比較特殊的是Upgrade,Connection,和Sec開(kāi)頭的幾個(gè)字段,那么如果請(qǐng)求握手的話,

Upgrade: websocket
Connection: Upgrade

是固定的要填寫的兩個(gè)鍵值對(duì)。
Sec-WebSocket-Key是一個(gè)16位的隨機(jī)值,經(jīng)過(guò)base64編碼后生成,給服務(wù)器進(jìn)行UUID連接再編碼后由客戶端檢查用。
Sec-WebSocket-Version是使用的版本號(hào)。
Sec-WebSocket-Protocol是選用的子協(xié)議,此字段為可選字段,由服務(wù)器選擇一個(gè)子協(xié)議與客戶端通信,子協(xié)議是由websocket承載的協(xié)議。

response例子如下

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

我們可以看到這是一個(gè)狀態(tài)碼為101的響應(yīng),響應(yīng)的頭內(nèi)容基本和request可以對(duì)應(yīng),Sec-WebSocket-Accept是服務(wù)端利用KeyUUID拼接后再進(jìn)行base64編碼產(chǎn)生的一個(gè)值,由客戶端進(jìn)行驗(yàn)證。
這樣,我們的連接時(shí)握手就完成了。

數(shù)據(jù)幀

基礎(chǔ)幀協(xié)議

因?yàn)橐恍┌踩脑颍瑥目蛻舳税l(fā)送到服務(wù)端的幀全部要與掩碼進(jìn)行異或運(yùn)算過(guò)才有效,而服務(wù)端發(fā)送到客戶端的幀不需要進(jìn)行異或運(yùn)算。

我們來(lái)看下官方的一幅幀結(jié)構(gòu)定義圖

幀結(jié)構(gòu)定義圖
幀結(jié)構(gòu)定義圖

接下來(lái)逐一解釋。

名稱 長(zhǎng)度 注釋
FIN 1bit 標(biāo)明這一幀是否是整個(gè)消息體的最后一幀
RSV1 RSV2 RSV3 1bit 保留位,必須為0,如果不為0,則標(biāo)記為連接失敗
opcode 4bit 操作位,定義這一幀的類型
Mask 1bit 標(biāo)明承載的內(nèi)容是否需要用掩碼進(jìn)行異或
Masking-key 0 or 4bytes 掩碼異或運(yùn)算用的key
Payload length 7bit or 7 +16bit or 7 + 64bit 承載體的長(zhǎng)度(后續(xù)會(huì)解釋為什么會(huì)有3種長(zhǎng)度)

如果從結(jié)構(gòu)角度講,那么websocket幀結(jié)構(gòu)就這么簡(jiǎn)單。

操作碼 (opcode)

定義
%x0 標(biāo)明這一個(gè)數(shù)據(jù)包是上一個(gè)數(shù)據(jù)包的延續(xù),它是一個(gè)延長(zhǎng)幀 (continuation frame)
%x1 標(biāo)明這個(gè)數(shù)據(jù)包是一個(gè)字符幀 (text frame)
%x2 標(biāo)明這個(gè)數(shù)據(jù)包是一個(gè)字節(jié)幀 (binary frame)
%x3-7 保留值,供未來(lái)的非控制幀使用
%x8 標(biāo)明這個(gè)數(shù)據(jù)包是用來(lái)告訴對(duì)方,我方需要關(guān)閉連接
%x9 標(biāo)明這個(gè)數(shù)據(jù)包是一個(gè)心跳請(qǐng)求 (ping)
%xA 標(biāo)明這個(gè)數(shù)據(jù)包是一個(gè)心跳響應(yīng) (pong)
%xB-F 保留至,供未來(lái)的控制幀使用

在websocket中,我們定義了幾種操作類型,也就是表明了數(shù)據(jù)包的行為,數(shù)據(jù)包大體可分為兩種,一種是字符數(shù)據(jù)包 (string),一種是字節(jié)數(shù)據(jù)包 (byte)不同的數(shù)據(jù)包使用不同的opcode來(lái)傳輸,opcode定義如下:
首先Opcode占用4bit的長(zhǎng)度

定義
%x0 標(biāo)明這一個(gè)數(shù)據(jù)包是上一個(gè)數(shù)據(jù)包的延續(xù),它是一個(gè)延長(zhǎng)幀 (continuation frame)
%x1 標(biāo)明這個(gè)數(shù)據(jù)包是一個(gè)字符幀 (text frame)
%x2 標(biāo)明這個(gè)數(shù)據(jù)包是一個(gè)字節(jié)幀 (binary frame)
%x3-7 保留值,供未來(lái)的非控制幀使用
%x8 標(biāo)明這個(gè)數(shù)據(jù)包是用來(lái)告訴對(duì)方,我方需要關(guān)閉連接
%x9 標(biāo)明這個(gè)數(shù)據(jù)包是一個(gè)心跳請(qǐng)求 (ping)
%xA 標(biāo)明這個(gè)數(shù)據(jù)包是一個(gè)心跳響應(yīng) (pong)
%xB-F 保留至,供未來(lái)的控制幀使用

關(guān)于掩碼 (Mask)

如果是客戶端發(fā)送到服務(wù)端的數(shù)據(jù)包,我們需要使用掩碼對(duì)payload的每一個(gè)字節(jié)進(jìn)行異或運(yùn)算,生成masked payload 才能被服務(wù)器讀取。
具體的運(yùn)算其實(shí)很簡(jiǎn)單。

假設(shè)payload長(zhǎng)度為pLen,mask-key長(zhǎng)度為mLeni作為payload的游標(biāo),j作為mask-key的游標(biāo),偽代碼如下:

for (i = 0; i < pLen; i++){
    int j = i % mLen;
    maskedPayload[j] = payload[j] ^ maskKey[j];
}

Payload長(zhǎng)度

Payload Length位占用了可選的7bit或者7 + 16bit 或者 7 + 64bit,這里是什么意思呢? MDN上有文章也是對(duì)websocket協(xié)議進(jìn)行了很好的闡述,先貼原文:

編寫websocket服務(wù)器

引用其中關(guān)于Payload length定義的一段文字:

讀解負(fù)載數(shù)據(jù)長(zhǎng)度

讀取負(fù)載數(shù)據(jù),需要知道讀到那里為止。因此獲知負(fù)載數(shù)據(jù)長(zhǎng)度很重要。這個(gè)過(guò)程稍微有點(diǎn)復(fù)雜,要以下這些步驟:

  1. 讀取9-15位 (包括9和15位本身),并轉(zhuǎn)換為無(wú)符號(hào)整數(shù)。如果值小于或等于125,這個(gè)值就是長(zhǎng)度;如果是 126,請(qǐng)轉(zhuǎn)到步驟 2。如果它是 127,請(qǐng)轉(zhuǎn)到步驟 3。
  2. 讀取接下來(lái)的 16 位并轉(zhuǎn)換為無(wú)符號(hào)整數(shù),并作為長(zhǎng)度。
  3. 讀取接下來(lái)的 64 位并轉(zhuǎn)換為無(wú)符號(hào)整數(shù),并作為長(zhǎng)度。

當(dāng)然我們這邊所使用的都是網(wǎng)絡(luò)字節(jié)序

關(guān)閉連接時(shí)的握手

關(guān)閉連接的時(shí)候,只用發(fā)送一個(gè)opcode為0x08的幀,payload中前2個(gè)字節(jié)寫入定義的code,后續(xù)寫入關(guān)閉連接的reason,那么一個(gè)關(guān)閉流程就握手就開(kāi)始,此處不再贅述。

總結(jié)

websocket協(xié)議整體看下來(lái)其實(shí)還是很簡(jiǎn)單,也為我們的工作節(jié)省了很多不必要的麻煩。也許我們并不需要了解它實(shí)現(xiàn)的細(xì)節(jié)(除非你正在開(kāi)發(fā)非瀏覽器的客戶端或者服務(wù)端)

總而言之,想要學(xué)習(xí)一個(gè)技術(shù),看原始規(guī)范果然會(huì)讓人暢快淋漓啊~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • <!DOCTYPE html> 查看源 window.WRM=window.WRM||{};window....
    SMSM閱讀 926評(píng)論 1 0
  • 導(dǎo)演要帶女演員去賓館,女演員有些不愿意,導(dǎo)演說(shuō):“我和你爸媽是好朋友,難道還會(huì)拿你怎么樣嗎!”女演員便從了。到了賓...
    Junee_e閱讀 7,423評(píng)論 1 2
  • 有一位伙伴來(lái)咨詢: 他在一家新媒體創(chuàng)業(yè)公司,去年畢業(yè)加入到現(xiàn)在快一年時(shí)間了。突然有點(diǎn)迷茫了。因?yàn)檫@一年來(lái),公司變化...
    陳sir閱讀 1,337評(píng)論 2 12
  • 首先要有靜 有靜一樣的,河 有靜一樣的,釣魚人 然后才有突然而起的風(fēng)暴 突然而起。兩只野鴨子 翅膀拍擊空氣,激...
    西雨2016閱讀 495評(píng)論 0 1

友情鏈接更多精彩內(nèi)容