WebSocket原理詳解(一)-CSDN博客
目錄
1.WebSocket協議的幀數據詳解
1.1.幀結構
1.2.生成數據幀
2.WebSocket協議控制幀結構詳解
2.1.關閉幀
2.2.ping幀
2.3.pong幀
3.WebSocket心跳機制
1.WebSocket協議的幀數據詳解
1.1.幀結構
????????WebSocket客戶端與服務器通信的最小單位是幀(frame),一條完整消息是由一個或多個幀組成的。數據交互時,發送方會將消息切割為多個幀發送給接收方,接收方接收到消息幀后會重新組裝為完整的消息。
????????當WebSocket接收方接收到一個數據幀時會根據FIN(數據幀中的一個標識,用來判斷當前幀是否當前消息的最后一幀)的值來判斷是否已經接收到消息的最后一個數據幀。當接收到消息的最后一幀時,即可對消息進行處理。
WebSocket 幀由多個字段組成,各字段按順序排列,這些字段共同構成了幀的頭部和可選的負載數據。幀的基本結構如下:
字段 | 長度(字節) | 描述 |
---|---|---|
FIN | 1 位 | 表示該幀是否為消息的最后一幀。1 表示是最后一幀,0 表示還有后續幀。 |
RSV1 - RSV3 | 各 1 位 | 保留位,默認值為 0。如果要使用,需要在握手階段進行協商。 |
Opcode | 4 位 | 定義幀的操作類型,如文本幀、二進制幀、關閉幀等。 |
Mask | 1 位 | 表示負載數據是否經過掩碼處理。客戶端發送的幀必須進行掩碼處理。 |
Payload length | 7 位、7 + 16 位或 7 + 64 位 | 表示負載數據的長度。如果值在 0 - 125 之間,直接使用 7 位表示;如果是 126,則后續 2 個字節表示長度;如果是 127,則后續 8 個字節表示長度。 |
Masking - key | 0 或 4 字節 | 如果?Mask ?位為 1,則存在 4 字節的掩碼密鑰,用于對負載數據進行掩碼處理。 |
各字段詳細解釋
1. FIN 字段
- 作用:用來指示當前幀是否為一個消息的最后一幀。在發送大消息時,消息可能會被拆分成多個幀,
FIN
?位可以幫助接收方判斷消息是否完整。 - 取值:
1
:表示這是消息的最后一幀。0
:表示還有后續幀。
2. RSV1 - RSV3 字段
- 作用:這三個保留位主要是為未來擴展 WebSocket 協議預留的。目前默認值都為 0,如果要使用這些位,需要在握手階段進行協商。
- 取值:通常為 0。
3. Opcode 字段
- 作用:定義了幀的操作類型,接收方根據?
Opcode
?的值來決定如何處理接收到的幀。 - 常見取值及含義:
0x0
:表示延續幀,用于繼續之前未完成的消息。0x1
:表示文本幀,負載數據為 UTF - 8 編碼的文本。0x2
:表示二進制幀,負載數據為二進制數據。0x3-0x7:保留
幀,留作未來非控制幀擴展使用0x8
:表示關閉幀,用于關閉 WebSocket 連接。0x9
:表示心跳檢測的 ping 幀。0xA
:表示心跳檢測的 pong 幀,是對 ping 幀的響應。0xB-0xF:保留
幀, 留作未來控制幀擴展使用
4. Mask 字段
- 作用:指示負載數據是否經過掩碼處理。為了提高安全性,客戶端發送的幀必須將負載數據進行掩碼處理,服務器發送的幀不需要進行掩碼處理。
- 取值:
1
:表示負載數據經過掩碼處理。0
:表示負載數據未經過掩碼處理。
5. Payload length 字段
- 作用:表示負載數據的長度。由于負載數據長度可能不同,該字段采用了可變長度的編碼方式。
- 取值及編碼方式:
- 如果值在 0 - 125 之間,直接使用 7 位表示負載數據的長度。
- 如果值為 126,則后續 2 個字節(16 位)表示負載數據的長度。
- 如果值為 127,則后續 8 個字節(64 位)表示負載數據的長度。
6. Masking - key 字段
- 作用:如果?
Mask
?位為 1,該字段存在,且長度為 4 字節。它是一個隨機生成的掩碼密鑰,用于對負載數據進行掩碼處理和解掩碼處理。 - 掩碼處理和解掩碼處理:假設?
P
?是原始的負載數據,M
?是掩碼密鑰,C
?是經過掩碼處理后的數據,則?C[i] = P[i] ^ M[i % 4]
,接收方可以使用相同的掩碼密鑰和異或運算對?C
?進行解掩碼得到?P
。
7. Payload data 字段
- 作用:實際傳輸的數據,可以是文本、二進制數據等,具體取決于?
Opcode
?的值。
示例
假設客戶端要發送一個簡單的文本消息 "Hello",以下是一個簡化的幀結構分析:
- FIN:由于這是消息的唯一一幀,
FIN
?為 1。 - RSV1 - RSV3:默認值為 0。
- Opcode:消息是文本,
Opcode
?為 0x1。 - Mask:客戶端發送的幀需要掩碼處理,
Mask
?為 1。 - Payload length:消息長度為 5 字節,小于 126,直接用 7 位表示,值為 5。
- Masking - key:隨機生成 4 字節的掩碼密鑰,例如?
0x12345678
。 - Payload data:將 "Hello" 進行掩碼處理后的數據。
1.2.生成數據幀
????????從服務器發往客戶端的數據也是同樣的數據幀,但是從服務器發送到客戶端的數據幀不需要掩碼的。我們自己需要去生成數據幀,解析數據幀的時候我們需要分片。
消息分片:
????????有時候數據需要分成多個數據包發送,需要使用到分片,也就是說多個數據幀來傳輸一個數據。比如將大數據分成多個數據包傳輸,分片的目的是允許發送未知長度的消息。這樣做的好處是:
- 大數據的傳輸可以分片傳輸,不用考慮到數據大小導致的長度標志位不夠的情況
- 和http的chunk一樣,可以邊生成數據邊傳遞消息,可以提高傳輸效率。
????????如果大數據不能被碎片化,那么發送端就必須將數據整個載入內存緩沖之中,然后進行計算長度等操作并發送。但是有了碎片化機制,服務器就可以選取適用的內存緩沖長度,然后當緩沖滿了之后就發送一個消息碎片。
分片規則:
- 如果一個消息不分片的話,那么該消息只有一幀(FIN為1,opcode非0);
- 如果一個消息分片的話,它的構成是由
- 1個起始幀:FIN為0,opcode非0
- 然后若干幀:FIN為0,opcode為0
- 1個結束幀:FIN為1,opcode為0
注意:
- 當前已經定義的控制幀包括 0x8(close)、0x9(Ping)、0xA(Pong)。控制幀可以出現在分片消息中間,但是控制幀不允許分片,控制幀是通過它的opcode的最高有效位是1去確定的。
- 組成消息的所有幀都是相同的數據類型,在第一幀中的opcode中指明。組成消息的碎片類型必須是文本,二進制,或者其他的保留類型。
2.WebSocket協議控制幀結構詳解
????????目前,控制幀的操作碼定義了0x08(關閉幀)、0x09(Ping幀)、0x0A(Pong幀)。0x0B-0x0F是為那些將來可能定義而目前尚未定義的控制幀預留的。
????????控制幀用于WebSocket協議交換狀態信息,控制幀可以插在消息片段之間。
注意:所有的控制幀的負載長度務必不大于125字節,并且禁止對控制幀進行分片處理。
2.1.關閉幀
????????關閉幀的操作碼Opcode是0x08。
????????關閉幀可能包含數據部分(應用數據幀),該部分表明了關閉的原因,例如:端點關閉、端點接收幀過大或端點收到的幀不符合預期。
- 如果有數據部分,則數據的前兩個字節必須是一個無符號整數(網絡字節序),該無符號整數表示了一個狀態碼,具體定義哪些關閉碼將在后面的文章中介紹。
- 在無符號整數后面,可能還有一個UTF-8編碼的數據,表示關閉原因,關閉原因由開發者自行定義(可選),并無規范。關閉原因并不一定是對人可讀的,但會對調試或傳遞相關信息起到一定的作用。由于數據不能保證人類可讀,所以客戶端一定不能將其顯示給用戶(會在關閉事件onclose中)
????????客戶端發送給服務器的關閉幀必須掩碼處理。應用程序在發送了一個關閉幀后,禁止再發送任何數據(此時處于CLOSING狀態)。
????????如果端點(客戶端或服務器)收到了一個關閉幀,并且之前沒有發送過關閉幀,則端點必須發送一個關閉幀作為響應。(當端點發送一個關閉幀回應時,通常會顯示它收到的狀態碼)。當端點可以發送關閉響應時應盡快發送關閉響應。一個端點可以延遲發送響應直到它的當前消息發送完畢(例如,已經發送了大多數的消息片段,則端點可能會在發送關閉響應幀前先將剩下的消息幀發送出去)。但不能保證對方在已經發送了關閉幀后還能夠繼續處理這些數據。
????????在雙方都已發送并接收了關閉幀后,端點需要斷掉WebSocket連接并且必須關閉底層的TCP連接。服務器必須立即切斷底層TCP連接,客戶端最好等待服務器斷開連接,但也可以在發送并接收了關閉幀后任何時候斷開連接,例如在一段時間內服務器仍沒有斷開TCP連接。
????????如果服務器和客戶端同時發送了關閉幀,兩端都會接收關閉幀,并且都需要斷開TCP連接。
關閉幀示例
假設客戶端要發送一個關閉幀,狀態碼為 1000(正常關閉),沒有額外的關閉原因。其幀結構如下:
- FIN:1
- RSV1 - RSV3:0 0 0
- Opcode:0x8
- Mask:1(客戶端發送)
- Payload length:2(狀態碼占 2 字節)
- Masking - key:隨機生成的 4 字節密鑰
- Payload data:狀態碼 1000(2 字節)
2.2.ping幀
????????Ping幀的操作碼為0x09。
????????Ping幀可以包含應用數據。
????????一旦接到了一個Ping幀,端點必須返回一個Pong幀作為響應,除非它收到了一個關閉幀。它應在可以發送時盡快發送Pong幀響應。
????????端點可以在連接建立后一直到連接關閉前任何時候發送Ping幀。
ping 幀示例
客戶端發送一個 ping 幀,包含自定義的心跳信息 "HEARTBEAT"。其幀結構如下:
- FIN:1
- RSV1 - RSV3:0 0 0
- Opcode:0x9
- Mask:1(客戶端發送)
- Payload length:9("HEARTBEAT" 長度為 9)
- Masking - key:隨機生成的 4 字節密鑰
- Payload data:經過掩碼處理的 "HEARTBEAT"
2.3.pong幀
????????Pong幀的操作碼為0x0A。
????????Pong幀必須與Ping幀擁有相同的應用數據部分。
????????如果端點收到了多個Ping幀,但還沒來的及全部回應,可以只回應最后一個Ping幀。
????????Pong幀可以在未收到Ping幀時就被發送,用作單向心跳包。
????????對未被請求的Pong幀(對方主動發送的Pong幀)進行回應是不需要的。
3.WebSocket心跳機制
????????心跳機制的核心是客戶端和服務器之間定期發送和接收特定的心跳消息。一般而言,客戶端和服務器中的一方會定期發送心跳消息(如 ping 幀),另一方收到后則立即回復相應的響應消息(如 pong 幀)。若在規定時間內未收到響應,就判定連接出現問題,進而進行重連或其他處理。
????????WebSocket 協議本身定義了 ping 幀和 pong 幀用于心跳檢測。
- ping 幀:由客戶端或服務器發送,用于主動檢測對方的連接狀態。
- pong 幀:是對 ping 幀的響應,當一方收到 ping 幀后,必須盡快發送一個 pong 幀,且 pong 幀的負載數據應與 ping 幀的負載數據相同。