HTML5(十二)——一文讀懂 WebSocket 原理


一、WebSocket 由來

WebSocket 是一個持久化的協議,通過第一次 HTTP Request 建立連接之后,再把通信協議升級成 websocket,保持連接狀態,后續的數據交換不需要再重復請求。websocket 可以看成一種類似 TCP/IP 的 socke t技術,在 web 應用中實現、並獲得同 TCP/IP 通信一樣的雙向通信功能,因此客戶端既和服務器可以發送消息也可以接收消息,同時還支持多路復用的功能,由於它借用了 HTTP 協議的一些概念,所以被稱為 WebSocket。

webSocket API定義了web應用和服務器進行通信的公共接口,具體的構造函數創建對象、對象的屬性、方法、事件及它的意義,在上一篇《HTML5(十一)——WebSocket 基礎教程》文章中已詳細介紹。

二、WebSocket 通信過程

WebSocket 協議可分為兩部分:握手階段和數據通信階段。

WebSocket 為應用層協議,定義在 TCP/IP 協議棧之上,連接服務器的 url 是以 ws 或 wss 開頭的。ws 開頭的默認TCP端口為80,wss 開頭的默認端口為443。

ws(websocket)是不安全的,容易被竊聽,只要別人知道你的ip和端口號,任何人都可以去連接通訊。

wss(web socket secure)是websocket的加密版本。

2.1、建立連接

客戶端去與服務器建立 TCP 連接,客戶端生成 websocket 對象,然后使用 API 建立連接,代碼如下:

let ws= new WebSocket('ws://localhost:8888')
ws.onopen = function(){
 console.log("連接")
}

2.2、握手階段

客戶端與服務器建立連接之后,客戶端發送握手請求,隨后服務器發送握手響應即完成握手階段。

客戶端握手請求如下:

'GET / HTTP/1.1',
 'Host: localhost:8888',
 'Connection: Upgrade',
 'Pragma: no-cache', 'Cache-Control: no-cache', 'Upgrade: websocket', 'Origin: file://', 'Sec-WebSocket-Version: 13', 'Accept-Encoding: gzip, deflate, br', 'Accept-Language: zh-CN,zh;q=0.9', 'Sec-WebSocket-Key: In1aAp/ya9Lkv+tsUtXLXQ==', 'Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits',

服務器握手響應如下:

Status Code: 101 Switching Protocols Connection: Upgrade sec-websocket-Accept: HBMDBbZMiS59r3aAITpGtJ64Mfc= Upgrade: websocket

2.3、數據通訊

WebSocket 握手連接成功之后。可以使用 send 進行發送數據,onmessage 接收數據,如下發送“你好”:

let ws= new WebSocket('ws://localhost:8888')
ws.onopen = function(){
 console.log("連接成功")
 ws.send("你好")
}
ws.onmessage = function(res){
 console.log('接收到的消息',res)
}

 

服務器打印接收到的數據,如:<Buffer 81 86 af 87 53 b4 4b 3a f3 51 0a 3a>。

websocket 在發送數據時,被組織為一串數據幀,然后進行發送。傳送的幀包含兩部分:數據幀和控制幀。數據幀可以攜帶文本數據或者二進制數據,控制幀包含關閉幀和 Ping/Pong 幀。

HTML5(十二)——一文讀懂 WebSocket 原理

 

  • FIN :1bit ,表示是消息的最后一幀,如果消息只有一幀那么第一幀也就是最后一幀。
  • RSV1,RSV2,RSV3:每個1bit,必須是0,除非擴展定義為非零。如果接受到的是非零值但是擴展沒有定義,則需要關閉連接。
  • Opcode:4bit,解釋Payload數據,規定有以下不同的狀態,如果是未知的,接收方必須馬上關閉連接。狀態如下:0x0(附加數據幀) 0x1(文本數據幀) 0x2(二進制數據幀) 0x3-7(保留為之后非控制幀使用) 0xB-F(保留為后面的控制幀使用) 0x8(關閉連接幀) 0x9(ping) 0xA(pong)
  •  Mask:1bit,掩碼,定義payload數據是否進行了掩碼處理,如果是1表示進行了掩碼處理。Masking-key域的數據即是掩碼密鑰,用於解碼PayloadData。客戶端發出的數據幀需要進行掩碼處理,所以此位是1。
  • Payload length:7位,7 + 16位,7+64位,payload數據的長度,如果是0-125,就是真實的payload長度,如果是126,那么接着后面的2個字節對應的16位無符號整數就是payload數據長度;如果是127,那么接着后面的8個字節對應的64位無符號整數就是payload數據的長度。
  • Masking-key:0到4字節,如果MASK位設為1則有4個字節的掩碼解密密鑰,否則就沒有。
  • Payload data:任意長度數據。包含有擴展定義數據和應用數據,如果沒有定義擴展則沒有此項,僅含有應用數據。

把接收到的buffer十六進制數據轉成二進制數據,控制幀與上述各個類型幀進行對比解析其意義。

2.4、關閉連接

任何一端可以關閉連接。客戶端關閉連接如下:

ws.close()

然后發送關閉幀給對方,通常會帶有關閉連接的狀態碼,常見的狀態碼如下:

  • 1000 連接正常關閉
  • 1001 端點離線,例如服務器down,或者瀏覽器已經離開此頁面
  • 1002 端點因為協議錯誤而中斷連接
  • 1003 端點因為受到不能接受的數據類型而中斷連接
  • 1004 保留
  • 1005 保留, 用於提示應用未收到連接關閉的狀態碼
  • 1006 端點異常關閉
  • 1007 端點收到的數據幀類型不一致而導致連接關閉
  • 1008 數據違例而關閉連接
  • 1009 收到的消息數據太大而關閉連接
  • 1010 客戶端因為服務器未協商擴展而關閉
  • 1011 服務器因為遭遇異常而關閉連接
  • 1015 TLS握手失敗關閉連接

三、websocket 實例

3.1、客戶端創建websocket對象,並建立連接之后發送數據。其中 ws 地址根據后台服務端口對應。

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Document</title>
</head>
<body>
 <script>
  let ws= new WebSocket('ws://localhost:8888')
  ws.onopen = function(){
   console.log("連接")
   ws.send("你好")
   }
   ws.onmessage = function(res){
    console.log('res',res)
  }
 </script>
</body>
</html>

3.2、使用 node.js 創建一個 websocket 服務,如創建一個serve.js文件,代碼如下:

const http = require("http")
const net = require("net") //原生的websocket
const crypto = require('crypto') // 安全性校驗
let serve = net.createServer(sock=>{
 //只握手一次
 sock.once('data',(data)=>{
  console.log("hand shake start") // 開始握手
  let str = data.toString();
  let lines = str.split('\r\n')
  //舍棄第一行和最后兩行
  lines = lines.slice(1,lines.length-2)
  let headers = {}
  lines.forEach(line=>{
   let [key,val] = line.split(': ')
   headers[key.toLowerCase()] = val
  })
  if( headers['upgrade']!= 'websocket' ){
    console.log("其他協議")
    sock.end()
  }else if(headers['sec-websocket-version']!=13){
   console.log("版本不對")
   sock.end()
  }else{
   let key = headers['sec-websocket-key']
   let mask = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
   //sha1(key+mask) -> base64 =>client
   let hash = crypto.createHash('sha1')
   hash.update(key+mask)
   let key2 = hash.digest('base64')
   sock.write(`HTTP/1.1 101 Switching Protocols\r\nUpgrade:websocket\r\nConnection:Upgrade\r\nsec-websocket-Accept:${key2}\r\n\r\n` )
   console.log("hand shake end") // 握手結束
   //真正的數據
   sock.on('data',res=>{
   console.log("真正接收數據",res)
   //數據解析
   let FIN = res[0]&0x001;
   let opcode = data[0]&0x0F0;
   let msak = data[1]&0x001;
   let payload = data[1]&0x0FE;          
  })
  }
 })
 //斷開
 sock.on('end',()=>{
  console.log("連接已斷開")
 })
})
serve.listen("8888")

 

使用命令 node serve.js 或node serve 啟動服務,服務啟動成功之后可以使用localhost:8888訪問服務。

啟動服務之后,訪問前邊創建的html文件訪問websocket服務。

四、websocket的優點

  • 第一次通過http建立連接之后,數據交互不用發送http請求,節省了帶寬資源。
  • websocket連接是雙向通信,服務器和客戶端既可接受也可發送消息。
  • websocket多路復用,幾個不同url可以復用一個websocket服務。
  • 是HTML5的技術之一,有巨大應用前景。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM