WebSocket

基本知識

WebSocket 是一種應用層協議,基于TCP協議;
WebSocket protocol 是HTML5一種新的協議。它是實現了瀏覽器與服務器全雙工通信(full-duplex)。

WebSocket通信過程

WebSocket通信過程

客戶端發起握手請求

客戶端發送握手請求內容

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

字段說明

  • Sec-WebSocket-Protocol:字段表示客戶端可以接受的子協議類型,也就是在Websocket協議上的應用層協議類型。上面可以看到客戶端支持chat和superchat兩個應用層協議,當服務器接受到這個字段后要從中選出一個協議返回給客戶端;
  • Upgrade:告訴服務器這個HTTP連接是升級的Websocket連接;
  • Connection:告知服務器當前請求連接是升級的;
  • Origin:該字段是用來防止客戶端瀏覽器使用腳本進行未授權的跨源攻擊,這個字段在WebSocket協議中非常重要。服務器要根據這個字段判斷是否接受客戶端的Socket連接。可以返回一個HTTP錯誤狀態碼來拒絕連接;
  • Sec-WebSocket-Key:為了表示服務器同意和客戶端進行Socket連接, 服務器端需要使用客戶端發送的這個Key進行校驗 ,然后返回一個校驗過的字符串給客戶端,客戶端驗證通過后才能正式建立Socket連接。服務器驗證方法是: 首先進行 Key + 全局唯一標示符(GUID)“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”連接起來,然后將連接起來的字符串使用SHA-1哈希加密,再進行base64加密,將得到的字符串返回給客戶端作為握手依據。其中GUID是一個對于不識別WebSocket的網絡端點不可能使用的字符串 。

客戶端發送握手請求的要求

  • 請求的WebSocket URI必須要是定義的有效的URI;
  • 如果客戶端已經有一個WebSocket連接到遠程服務器端,不論是否是同一個服務器,客戶端必須要等待上一個連接關閉后才能發送新的連接請求,也就是同一客戶端一次只能存在一個WebSocket連接。如果想同一個服務器有多個連接,客戶端必須要串行化進行。如果客戶端檢測到多個到不同服務器的連接,應該限制一個最大連接數,在web瀏覽器中應該設定最多可以打開的標簽頁的數目。這樣可以防止到遠程服務器的DDOS攻擊,但這是對到多個服務器的連接,如果是到同一個服務器連接,并沒有數目限制;
  • 如果使用了代理服務器,那么客戶端建立連接的時候需要告知代理服務器向目標服務器打開TCP連接;
  • 如果連接沒有打開,一定是某一方出現錯誤,此時客戶端必須要關閉再次連接的嘗試;
  • 連接建立后,握手必須要是一個有效的HTTP請求;
  • 請求的方式必須是GET,HTTP協議的版本至少是1.1;
  • Upgrade字段必須包含而且必須是"websocket",Connection字段必須內容必須是“Upgrade”;
  • Sec-Websocket-Version必須,而且必須是13。

服務端回應握手請求

握手服務端返回內容

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

字段說明

  • 首行返回的是HTTP/1.1協議版本和狀態碼101,表示變換協議(Switching Protocol);
  • Upgrade 和 Connection:這兩個字段是服務器返回的告知客戶端同意使用升級并使用websocket協議,用來完善HTTP升級響應;
  • Sec-WebSocket-Accept:服務器端將加密處理后的握手Key通過這個字段返回給客戶端表示服務器同意握手建立連接;
  • Sec-Websocket-Procotol:服務器選擇的一個應用層協議;

在請求中的“Sec-WebSocket-Key”是隨機的,服務器端會用這些數據來構造出一個SHA-1的信息摘要。把“Sec-WebSocket-Key”加上一個魔幻字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”。使用SHA-1加密,之后進行BASE-64編碼,將結果做為“Sec-WebSocket-Accept”頭的值,返回給客戶端。

服務器端響應步驟

  • 解析握手請求頭:獲取握手依據Key并進行處理,檢測HTTP的GET請求和版本是否準確,Host字段是否有權限,Upgrade字段中websocket是一個與大小寫無關的ASCII字符串,Connection字段是一個大小寫無關的"Upgrade"ASCII字符串,Websocket協議版本必須為13,其他的關于Origin、Protocol和Extensions可選。

  • 發送握手響應頭:檢測是否是wss協議連接,如果是就是用TLS握手連接,否則就是普通連接。服務器可以添加額外的驗證信息到客戶端進行驗證。當進行一系列驗證之后,服務器必須返回一個有效的HTTP響應頭。響應頭中每一行一個字段,結束必須為“\r\n”,使用的ABNF語法。

WebSocket關閉握手

當WebSocket關閉時,終止連接的端點可以發送一個數字代碼,以及一個表示選擇關閉套接字原因的字符串。代碼和原因編碼為具有關閉操作碼(8)的一個幀的載荷。數字代碼用一個16位無符號整數表示,原因則是一個UTF-8編碼的短字符串。RFC 6455定義了多種特殊的關閉代碼。代碼1000~1015規定用于WebSocket連接層。這些代碼表示網絡中或者協議中的某些故障。

WebSocket關閉代碼的定義

代碼 描述 何時使用
1000 正常關閉 當你的會話成功完成時發送這個代碼
1001 離開 因應用程序離開且不期望后續的連接嘗試而關閉連接時,發送這一代碼。服務器可能關閉,或者客戶端應用程序可能關閉
1002 協議錯誤 當因協議錯誤而關閉連接時發送這一代碼
1003 不可接受的數據類型 當應用程序接收到一條無法處理的意外類型消息時發送這一代碼
1004 保留 不要發送這一代碼。根據RFC 6455,這個狀態碼保留,可能在未來定義
1005 保留 不要發送這一代碼。WebSocket API用這個代碼表示沒有接收到任何代碼
1006 保留 不要發送這一代碼。WebSocket API用這個代碼表示連接異常關閉
1007 無效數據 在接收一個格式與消息類型不匹配的消息之后發送這一代碼。如果文本消息包含錯誤格式的UTF-8數據,連接應該用這個代碼關閉
1008 消息違反政策 黨應用程序由于其他代碼所包含的原因終止連接,或者不希望泄露消息無法處理的原因時,發送這一代碼
1009 消息過大 當接受的消息太大,應用程序無法處理時,發送這一代碼(記住,幀的載荷長度最多為64字節。即使你有一個大服務器,有些消息也仍然太大)
1010 需要擴展 當應用程序需要一個或者多個服務器無法協商特殊擴展時,從客戶端發送這一代碼
1011 意外情況 當應用程序由于不可預見的原因,無法繼續處理連接時,發送這一代碼
1015 TLS失敗(保留) 不要發送這個代碼。WebSocket API用這個代碼表示TLS在WebSocket握手之前失敗

WebSocket URI

定義的兩個協議框架ws和wss與http類似,而且各自部分的要求也是在HTTP協議中使用的一樣,各自的URI如下:

ws-URI = "ws:" "http://" host [ ":" port ] path [ "?" query ]
wss-URI = "wss:" "http://" host [ ":" port ] path [ "?" query ]
其中port是可選項,query前接“?”

WebSocket API

// 創建一個Socket實例
var socket = new WebSocket('ws://localhost:8080', [protocal]); 
// 連接建立,即握手成功觸發的事件;
socket.onopen = function(event) { 
// 發送一個初始化消息
socket.send('I am the client and I\'m listening!'); 
// 收到服務器消息時觸發的事件
socket.onmessage = function(event) { 
console.log('Client received a message',event); 
}; 
// 異常觸發的事件
socket.onerror=function(event){
console.log(,"error: " event);
}
// 關閉連接觸發的事件
socket.onclose = function(event) { 
console.log('Client notified socket has closed',event); 
}; 
// 關閉連接
socket.close();

Socket.readyState只讀屬性readyState表示連接的狀態。有以下取值:

CONNECTING(0) 表示連接尚未建立;
OPEN(1) 表示連接已建立,可以進行通信;
CLOSING(2) 表示連接正在進行關閉握手;
CLOSED(3) 表示連接已經關閉或者連接不能打開。

WebSocket包結構

WebSocket包結構

FIN: 最高位用于描述消息是否結束,如果為1則該消息為消息尾部,如果為零則還有后續數據包;
RSV: 后面3位是用于擴展定義的,如果沒有擴展約定的情況則必須為0。
OPCODE: 最低4位用于描述消息類型,消息類型暫定有15種,其中有幾種是預留設置,操作碼定義如下:

操作碼 消息載荷類型 描述
0 連續消息標識 表示連續消息片段
1 文本 消息的數據類型為文本
2 二進制 消息的數據類型為二進制
3~7 保留 為將來的非控制消息片斷保留操作碼
8 關閉 客戶端或者服務器向對方發送關閉握手
9 ping 客戶端或者服務器向對方發送ping
A pong 客戶端或者服務器向對方發送pong
B~F 保留 為將來的控制消息片斷的保留操作碼

Mask:最高位用0或1來描述是否有掩碼處理,也就是所說的屏蔽。從瀏覽器向服務器發送的WebSocket幀內容進行了“屏蔽”,以混淆其內容。屏蔽的目的不是阻止竊聽,而是為了不常見的安全原因,以及改進和現有HTTP代理的兼容性。
Payload length: 傳輸數據的長度,以字節的形式表示:7位、7+16位、或者7+64位。如果這個值以字節表示是0-125這個范圍,那這個值就表示傳 輸數據的長度;如果這個值是126,則隨后的兩個字節表示的是一個16進制無符號數,用來表示傳輸數據的長度;如果這個值是127,則隨后的是8個字節表示的一個64位無符號數,這個數用來表示傳輸數據的長度。多字節長度的數量是以網絡字節的順序表示。負載數據的長度為擴展數據及應用數據之和,擴展數據的長度可能為0,因而此時負載數據的長度就為應用數據的長度。
Masking-key:0或4個字節,客戶端發送給服務端的數據,都是通過內嵌的一個32位值作為掩碼的;掩碼鍵只有在掩碼位設置為1的時候存在。
Payload data: (x+y)位,負載數據為擴展數據及應用數據長度之和。
Extension data:x位,如果客戶端與服務端之間沒有特殊約定,那么擴展數據的長度始終為0,任何的擴展都必須指定擴展數據的長度,或者長度的計算方式,以及在握手時如何確定正確的握手方式。如果存在擴展數據,則擴展數據就會包括在負載數據的長度之內。
Application data:y位,任意的應用數據,放在擴展數據之后,應用數據的長度=負載數據的長度-擴展數據的長度。

WSS

wss是安全的WebSocket通信協議

注意問題

  1. 握手過程中,HTTP request method 必須是GET,協議應不小于1.1
  2. 所有數據傳輸都是UTF-8編碼的數據,當一端接收到的字節流數據不是一個有效的UTF-8數據流,那個么接收到的這一方必須要馬上關閉連接。這個規則在開始握手一直到所有的數據交換過程都要進行驗證。

瀏覽器支持

下面是主流瀏覽器對 HTML5 WebSocket 的支持情況:
http://caniuse.com/#search=websocket

瀏覽器 支持情況
Chrome Supported in version 4+
Firefox Supported in version 4+
Internet Explorer Supported in version 10+
Opera Supported in version 10+
Safari Supported in version 5+

參考

WebSocket 教程

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374