游戲開發教程(元宇宙游戲開發教程-前言)
元宇宙(Metaverse)是利用科技手段進行鏈接與創造的,與現實世界映射與交互的虛擬世界,具備新型社會體系的數字生活空間。
元宇宙本質上是對現實世界的虛擬化、數字化過程,需要對內容生產、經濟系統、用戶體驗以及實體世界內容等進行大量改造。但元宇宙的發展是循序漸進的,是在共享的基礎設施、標準及協議的支撐下,由眾多工具、平臺不斷融合、進化而最終成形。
以上是百度百科的內容,看不懂也沒關系。一句話概括,無論多么復雜的宇宙,都是從無到有的過程。那么我們現在就從最原始最基礎的“游戲開發”做起。
開發過程初步規劃為以下幾個階段:
1.先開發一個功能多元化的網絡游戲。
2.為游戲增加區塊鏈虛擬貨幣系統。
3.開發NFT認證系統,并能兼容傳統游戲。
4.開發虛擬與現實接軌的互動接口。
5.接納原有傳統游戲的生態體系,使之成為元宇宙游戲的一部分。
什么是功能多元化的網絡游戲呢?與傳統網絡游戲不同的是,其功能極其集中化,比如虛擬社區、虛擬經濟、虛擬商城、虛擬辦公等等新興的互聯網形式,都是在其各自的領域,獨立發展的。而功能多元化的游戲就是把眾多元素集中于一個系統下。
可能還會有很多人不太明白具體意思。就比方說,以前開發一個游戲,會有專門負責用戶登陸的“登陸服務器”(LoginServer),專門負責游戲處理的游戲服務器(GameServer),專門負責游戲公告宣傳和用戶注冊等等功能的網站服務器(WebServer),專門負責數據存取傳輸的數據服務器(SQLServer、MySQLServer),至于商城社區、動態均衡、服務器集群等等就不再列舉了。
而功能多元化的游戲就只能有一個功能集中于一體的服務程序。比如以前開發網站可以用ASP、PHP、JSP、ASP.NET等腳本語言去開發。而多元游戲服務程序就不能使用任何已知的腳本語言來開發,必須由自身來完成。最終的服務器端只有一個服務程序,包含元宇宙游戲所能涉及到的全部功能,自我完成多分區多線路的游戲分發和調配工作。
為什么一定要強調功能集中化呢?因為元宇宙是要求虛擬與現實相結合的全新宇宙時空。就比如說我們現實生活的宇宙,所有物理定律、公理常數等等,一旦定義就不能再被更改。而傳統的很多編程和腳本語言都是無法跨平臺的,即使能跨平臺,也是它執行它的,你執行你的。元宇宙游戲要求極高的瞬時性,如果還在使用傳統的編程開發方式,用一個個獨立的程序代碼去執行,是無法完成瞬間統一協調的。
然而目前能夠滿足元宇宙游戲的基礎設施還沒有出現。比如在軟件開發方面,還沒有一種編程軟件可以完成所有人類使用程序的應用需要。而在硬件方面,即使是5G全方位覆蓋也無法滿足元宇宙游戲的帶寬需求。
但是我們也不能因為這些不足就不去做了呀,現在離真正的元宇宙還十分遙遠。我們可以先初步開發相對接近于元宇宙的游戲,然后還要不斷的調試、改進、完善和升級。就比如我們現在的宇宙在“大爆炸”那一瞬間就產生了一切發展的定數、天理、常規等等統稱為“道”的東西。但是在“大爆炸”之前呢?誰知道設計和創造宇宙的那個“無名”,經歷了多少次的修改,多少次的重啟,才有了我們現在能認識到的精美絕倫的宇宙。
現在規劃元宇宙都是在制定統一的標準協議,但是現有的各種操作系統根本無法“統一標準”。所以元宇宙是要求設計全新概念的操作系統。我們要等到這些齊備了,豈不是要等到猴年馬月?
為什么“他們”說要全新概念的操作系統呢?網上的解釋多了去了,我只從現在即將面臨的一些問題,來反映一下具體的情況。就比如說游戲在跨平臺時,最基本的文字傳輸就面臨標準不統一的問題。新興的平臺多數采用UTF-8這個標準,但是在Win系統下的軟件都只有ANSI和UNICODE這兩個標準。如果你使用可以跨平臺的編程或腳本語言,一定是采用統一的一個標準。而我們實際上必須所有標準都要兼顧,否則就只能等到全球人類都換成統一的一個操作系統時,才能開發元宇宙了。
廢話不多說,我們開始干活。首先我們要自己設計網站服務器,可不是用傳統腳本語言。是不用任何現成的工具,自己去實現。這在現有編程體系里,都沒資料說網站服務器如何自己寫。那么我們只能采用最新的編程軟件——SEC中文編程來開發,因為它是不依賴于任何現有編程語言的框架,可以從零開始搭建完全獨立且又開放兼容的軟件程序。下載 z5x.cn/Sec.rar
這個網站服務器可不是基于任何你聽說過的腳本語言,所以你現在不要去想你以前會的網站代碼怎么寫。那么我們先來熟悉一下WebSocket協議吧,先來熱熱身,醒醒腦。提前預告一下,這個協議我猜你短時間看不懂,今天時間有限,明天我開始實戰教學。在SEC編程里,開發WebSocket的網站服務器,核心代碼大約十幾行,包括封包處理、協議解析、SHA1加密、ANSI和UNICODE到UTF-8的相互轉換,密文解密和傳輸等等,我都會逐一介紹,你只要復制我提供的代碼,直接編譯即可。
還有汪詰老師有專門科普元宇宙的詳細視頻,他主要涉及的是元宇宙的基本原理和概念知識,比如會提及區塊鏈、SHA256簽名加密等等相關知識點,但不會講實際應用的部分。我可能講得沒他那么生動,但是我每給你一段代碼都會盡量詳細描述它的作用原理和如何應用。
通常 Web 應用的交互模式是由客戶端向服務端發送 HTTP 請求, 服務端根據客戶端的的請求返回相應的數據, 在這樣的交互模式下, 通信雙方并不是對等的, 因為所有的請求都是由客戶端主動發起, 對于 HTTP/1.x 協議 [RFC 1945], [RFC 2616] 來說, 協議本身并不提供服務端向客戶端主動推送數據的機制, 因此基于 HTTP/1.x 的 Web 應用, 若需要獲取服務端的數據或狀態只能采用不斷輪詢 (Long Polling) 的方式, 最典型的例子如持續集成軟件 Jenkins, 在 Job 構建過程中需要在瀏覽器向用戶展示實時的 Console Output, 如果你在構建過程中進入瀏覽器的開發者模式便可以看到 Jenkins 采用周期性地向服務端發送請求以拉取實時的 Console 輸出數據, 再例如一些基于 Web 的網絡游戲, 例如 FPS 類游戲, 客戶端需要知道當前實時的全局狀態, 如其它玩家當前的坐標, 裝備等, 如果使用 HTTP/1.x 協議則只能采用不斷輪詢服務器的方式以獲得最新的狀態數據, 這種方式一方面效率不高, 而且不夠實時, 消息的實時性取決于兩次輪詢的時間差 (Gap), 最壞情況下需要晚于 1 個 Gap 才能拉到最新的數據, 另一方面頻繁地輪詢也增加了服務端額外的負載, 客戶端需要單獨維持一個連接用于輪詢服務器狀態, WebSocket 協議便是為了解決這個問題, WebSocket 協議提供了一種全雙工的通信機制, 服務端可以主動向客戶端推送數據, WebSocket 協議采用了 HTTP 協議來握手, 與 HTTP 使用相同的默認端口, 這一切都是為了兼容現有的 HTTP 組件或代理, 但 WebSocket 與 HTTP 是相互獨立的協議, 二者并不存在上下的層級關系, WebSocket 的正式協議文檔為 [RFC 6455], 本文全面討論 WebSocket 協議的設計與工作原理
1. WebSocket 協議概述
WebSocket 協議主要為了解決基于 HTTP/1.x 的 Web 應用無法實現服務端向客戶端主動推送的問題, 為了兼容現有的設施, WebSocket 協議使用與 HTTP 協議相同的端口, 并使用 HTTP Upgrade 機制來進行 WebSocket 握手, 當握手完成之后, 通信雙方便可以按照 WebSocket 協議的方式進行交互
WebSocket 使用 TCP 作為傳輸層協議, 與 HTTP 類似, WebSocket 也支持在 TCP 上層引入 TLS 層, 以建立加密數據傳輸通道, 即 WebSocket over TLS, WebSocket 的 URI 與 HTTP URI 的結構類似, 對于使用 80 端口的 WebSocket over TCP, 其 URI 的一般形式為 ws://host:port/path/query 對于使用 443 端口的 WebSocket over TLS, 其 URI 的一般形式為 wss://host:port/path/query
在 WebSocket 協議中, 幀 (frame) 是通信雙方數據傳輸的基本單元, 與其它網絡協議相同, frame 由 Header 和 Payload 兩部分構成, frame 有多種類型, frame 的類型由其頭部的 Opcode 字段 (將在下面討論) 來指示, WebSocket 的 frame 可以分為兩類, 一類是用于傳輸控制信息的 frame (如通知對方關閉 WebSocket 連接), 一類是用于傳輸應用數據的 frame, 使用 WebSocket 協議通信的雙方都需要首先進行握手, 只有當握手成功之后才開始使用 frame 傳輸數據
2. WebSocket 握手
當客戶端想要使用 WebSocket 協議與服務端進行通信時, 首先需要確定服務端是否支持 WebSocket 協議, 因此 WebSocket 協議的第一步是進行握手, WebSocket 握手采用 HTTP Upgrade 機制, 客戶端可以發送如下所示的結構發起握手 (請注意 WebSocket 握手只允許使用 HTTP GET 方法):
GET /chat HTTP/1.1Host: server.example.comUpgrade: websocketConnection: UpgradeSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Origin: http://example.comSec-WebSocket-Protocol: chat, superchatSec-WebSocket-Version: 13
在 HTTP Header 中設置 Upgrade 字段, 其字段值為 websocket, 并在 Connection 字段指示 Upgrade, 服務端若支持 WebSocket 協議, 并同意握手, 可以返回如下所示的結構:
HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-WebSocket-Protocol: chatSec-WebSocket-Version: 13
我們來詳細討論 WebSocket 的握手細節, 客戶端發起握手時除了設置 Upgrade 之外, 還需要設置其它的 Header 字段
| Sec-WebSocket-Key |, 必傳, 由客戶端隨機生成的 16 字節值, 然后做 base64 編碼, 客戶端需要保證該值是足夠隨機, 不可被預測的 (換句話說, 客戶端應使用熵足夠大的隨機數發生器), 在 WebSocket 協議中, 該頭部字段必傳, 若客戶端發起握手時缺失該字段, 則無法完成握手
| Sec-WebSocket-Version |, 必傳, 指示 WebSocket 協議的版本, RFC 6455 的協議版本為 13, 在 RFC 6455 的 Draft 階段已經有針對相應的 WebSocket 實現, 它們當時使用更低的版本號, 若客戶端同時支持多個 WebSocket 協議版本, 可以在該字段中以逗號分隔傳遞支持的版本列表 (按期望使用的程序降序排列), 服務端可從中選取一個支持的協議版本
| Sec-WebSocket-Protocol |, 可選, 客戶端發起握手的時候可以在頭部設置該字段, 該字段的值是一系列客戶端希望在于服務端交互時使用的子協議 (subprotocol), 多個子協議之間用逗號分隔, 按客戶端期望的順序降序排列, 服務端可以根據客戶端提供的子協議列表選擇一個或多個子協議
| Sec-WebSocket-Extensions |, 可選, 客戶端在 WebSocket 握手階段可以在頭部設置該字段指示自己希望使用的 WebSocket 協議拓展
服務端若支持 WebSocket 協議, 并同意與客戶端握手, 則應返回 101 的 HTTP 狀態碼, 表示同意協議升級, 同時應設置 Upgrade 字段并將值設置為 websocket, 并將 Connection 字段的值設置為 Upgrade, 這些都是與標準 HTTP Upgrade 機制完全相同的, 除了這些以外, 服務端還應設置與 WebSocket 相關的頭部字段:
| Sec-WebSocket-Accept |, 必傳, 客戶端發起握手時通過 | Sec-WebSocket-Key | 字段傳遞了一個將隨機生成的 16 字節做 base64 編碼后的字符串, 服務端若接收握手, 則應將該值與 WebSocket 魔數 (Magic Number) "258EAFA5-E914-47DA- 95CA-C5AB0DC85B11" 進行字符串連接, 將得到的字符串做 SHA-1 哈希, 將得到的哈希值再做 base64 編碼, 最終的值便是該字段的值, 舉例來說, 假設客戶端傳遞的 Sec-WebSocket-Key 為 "dGhlIHNhbXBsZSBub25jZQ==", 服務端應首先將該字符串與 WebSocket 魔數進行字符串拼接, 得到 "dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA- C5AB0DC85B11", 然后對該字符串做 SHA-1 哈希運算得到哈希值 0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea, 然后對該哈希值做 base64 編碼, 最終得到 Sec-WebSocket-Accept 的值為 s3pPLMBiTxaQ9kYGzzhZRbK+xOo=, 當客戶端收到服務端的握手響應后, 會做同樣的運算來校驗該值是否符合預期, 以便于判斷服務端是否真的支持 WebSocket 協議, 設置這個環節的目的就是為了最終校驗服務端對 WebSocket 協議的支持性, 因為單純使用 Upgrade 機制, 對于一些沒有正確實現 HTTP Upgrade 機制的 Web Server, 可能也會返回預期的 Upgrade, 但實際上它并不支持 WebSocket, 而引入 WebSocket 魔數并進行這一系列操作后可以很大程度上確定服務端確實支持 WebSocket 協議
| Sec-WebSocket-Protocol |, 可選, 若客戶端在握手時傳遞了希望使用的 WebSocket 子協議, 則服務端可在客戶端傳遞的子協議列表中選擇其中支持的一個, 服務端也可以不設置該字段表示不希望或不支持客戶端傳遞的任何一個 WebSocket 子協議
| Sec-WebSocket-Extensions |, 可選, 與 Sec-WebSocket-Protocol 字段類似, 若客戶端傳遞了拓展列表, 可服務端可從中選擇其中一個做為該字段的值, 若服務端不支持或不希望使用這些擴展, 則不設置該字段
| Sec-WebSocket-Version |, 必傳, 服務端從客戶端傳遞的支持的 WebSocket 協議版本中選擇其中一個, 若客戶端傳遞的所有 WebSocket 協議版本對服務端來說都不支持, 則服務端應立即終止握手, 并返回 HTTP 426 狀態碼, 同時在 Header 中設置 | Sec-WebSocket-Version | 字段向客戶端指示自己所支持的 WebSocket 協議版本列表
服務端若接收客戶端的握手, 便按上述所表述的規則向客戶端返回握手響應, 客戶端對服務端返回的握手響應做校驗, 若校驗成功, 則 WebSocket 握手成功, 之后雙方就可以開始進行雙向的數據傳輸。客戶端在發起握手后必須處于阻塞狀態, 換句話說, 客戶端必須等待服務端發回響應之后才允許開始數據傳遞, 客戶端對服務端的握手響應的校驗機制如下:
客戶端應首先檢查服務端返回的狀態碼是否為 101, 只有在 HTTP 狀態碼為 101 時才代表服務端同意了協議升級, 對于其它類型的狀態碼, 客戶端應根據 HTTP 狀態碼的語義做相應的處理
客戶端應檢查服務端返回的響應是否包含 Upgrade 字段, 若缺失, 代表 Upgrade 未成功, 客戶端應終止 WebSocket 握手
客戶端應檢查 Upgrade 字段的值是否為 websocket (該字段是大小寫不敏感的, 如 websocket, WebSocket, webSocket 等都是合法的), 若不是, 客戶端應終止 WebSocket 握手
客戶端應采用如上所表述的方式校驗服務端返回的 Sec-WebSocket-Accept 字段的值是否合法, 若該字段不存在或值不符合預期, 則客戶端應終止 WebSocket 握手
若服務端返回的 Header 中包含 Sec-WebSocket-Extensions, 但其字段的值并不在客戶端最初向服務端發起握手時傳遞的 Sec-WebSocket-Extensions 的值列表中, 則客戶端應終止 WebSocket 握手
若服務端返回的 Header 中包含 Sec-WebSocket-Protocol, 但該字段的值并不在客戶端最初向服務端發起握手時傳遞的 Sec-WebSocket-Protocol 的值列表中, 則客戶端應終止 WebSocket 握手
若客戶端校驗服務端的握手響應通過, 則 WebSocket 握手階段完成, 接下來雙方就可以進行 WebSocket 的雙向數據傳輸了
3. WebSocket 數據幀 (frame)
WebSocket 以 frame 為單位傳輸數據, frame 是客戶端和服務端數據傳輸的最小單元, 當一條消息過長時, 通信方可以將該消息拆分成多個 frame 發送, 接收方收到以后重新拼接、解碼從而還原出完整的消息, 在 WebSocket 中, frame 有多種類型, frame 的類型由 frame 頭部的 Opcode 字段指示, WebSocket frame 的結構如下所示:
該結構的字段語義如下:
FIN, 長度為 1 比特, 該標志位用于指示當前的 frame 是消息的最后一個分段, 因為 WebSocket 支持將長消息切分為若干個 frame 發送, 切分以后, 除了最后一個 frame, 前面的 frame 的 FIN 字段都為 0, 最后一個 frame 的 FIN 字段為 1, 當然, 若消息沒有分段, 那么一個 frame 便包含了完成的消息, 此時其 FIN 字段值為 1
RSV 1 ~ 3, 這三個字段為保留字段, 只有在 WebSocket 擴展時用, 若不啟用擴展, 則該三個字段應置為 1, 若接收方收到 RSV 1 ~ 3 不全為 0 的 frame, 并且雙方沒有協商使用 WebSocket 協議擴展, 則接收方應立即終止 WebSocket 連接
Opcode, 長度為 4 比特, 該字段將指示 frame 的類型, RFC 6455 定義的 Opcode 共有如下幾種:
0x0, 代表當前是一個 continuation frame
0x1, 代表當前是一個 text frame
0x2, 代表當前是一個 binary frame
0x3 ~ 7, 目前保留, 以后將用作更多的非控制類 frame
0x8, 代表當前是一個 connection close, 用于關閉 WebSocket 連接
0x9, 代表當前是一個 ping frame (將在下面討論)
0xA, 代表當前是一個 pong frame (將在下面討論)
0xB ~ F, 目前保留, 以后將用作更多的控制類 frame
Mask, 長度為 1 比特, 該字段是一個標志位, 用于指示 frame 的數據 (Payload) 是否使用掩碼掩蓋, RFC 6455 規定當且僅當由客戶端向服務端發送的 frame, 需要使用掩碼覆蓋, 掩碼覆蓋主要為了解決代理緩存污染攻擊 (更多細節見 RFC 6455 Section 10.3)
Payload Len, 以字節為單位指示 frame Payload 的長度, 該字段的長度可變, 可能為 7 比特, 也可能為 7 + 16 比特, 也可能為 7 + 64 比特. 具體來說, 當 Payload 的實際長度在 [0, 125] 時, 則 Payload Len 字段的長度為 7 比特, 它的值直接代表了 Payload 的實際長度; 當 Payload 的實際長度為 126 時, 則 Payload Len 后跟隨的 16 位將被解釋為 16-bit 的無符號整數, 該整數的值指示 Payload 的實際長度; 當 Payload 的實際長度為 127 時, 其后的 64 比特將被解釋為 64-bit 的無符號整數, 該整數的值指示 Payload 的實際長度
Masking-key, 該字段為可選字段, 當 Mask 標志位為 1 時, 代表這是一個掩碼覆蓋的 frame, 此時 Masking-key 字段存在, 其長度為 32 位, RFC 6455 規定所有由客戶端發往服務端的 frame 都必須使用掩碼覆蓋, 即對于所有由客戶端發往服務端的 frame, 該字段都必須存在, 該字段的值是由客戶端使用熵值足夠大的隨機數發生器生成, 關于掩碼覆蓋, 將下面討論, 若 Mask 標識位 0, 則 frame 中將設置該字段 (注意是不設置該字段, 而不僅僅是不給該字段賦值)
Payload, 該字段的長度是任意的, 該字段即為 frame 的數據部分, 若通信雙方協商使用了 WebSocket 擴展, 則該擴展數據 (Extension data) 也將存放在此處, 擴展數據 + 應用數據, 它們的長度和便為 Payload Len 字段指示的值
4. WebSocket 掩碼算法
RFC 6455 規定所有由客戶端發往服務端的 WebSocket frame 的 Payload 部分都必須使用掩碼覆蓋, 這是為了避免代理緩存污染攻擊 (更多細節見 RFC 6455 Section 10.3), 若服務端接收到沒有使用掩碼覆蓋的 frame, 服務端應立即終止 WebSocket 連接, 掩碼覆蓋只針對 frame 的 Payload 部分, 掩碼覆蓋不會改變 Payload 的長度, 掩碼覆蓋的算法如下:
客戶端使用熵值足夠高的隨機數生成器隨機生成 32 比特的 Masking-Key
以字節為步長遍歷 Payload, 對于 Payload 的第 i 個字節, 首先做 i MOD 4 得到 j, 則掩碼覆蓋后的 Payload 的第 i 個字節的值為原先 Payload 第 i 個字節與 Masking-Key 的第 j 個字節做按位異或操作
我們以 original-octet-i 表示未覆蓋前的 Payload 的第 i 個字節, 以 transformed-octet-i 表示覆蓋后的 Payload 的第 i 個字節, 以 masking-key-octet-j 表示 Masking-Key 的第 j 個字節, 那么上述算法的操作可以用如下兩個式子表示:
j = i % 4transformed-octet-i = original-octet-i XOR masking-key-octet-j
服務端收到客戶端的 frame 后, 首先檢查 Mask 標志位是否為 1, 若不是則應立即終止握手, 然后根據 Masking-Key 字段的值重復上述操作便可以得到原先的 Payload 數據
5. WebSocket 消息分片
當要發送的一條消息過長或者消息是實時產生并不能預測具體的長度時, 客戶端可將消息進行分片, 構成一個 frame 后便可以發往服務端, 分片的另一個考慮是為了復用底層的 TCP 連接, 當客戶端有多份相互獨立的數據需要發送時, 消息分片可以實現在一條 TCP 鏈路上的復用, 多份數據可以并發地發往服務端, 如果讀者了解過 HTTP/2 [RFC 7540] 便可以知道這里也是 HTTP/2 的做法, 但 RFC 6455 并沒有具體指出如何實現 WebSocket 分片消息的并發傳送, 在 HTTP/2 中, 并發傳送是通過 Stream 來關聯的, 根據 Stream Identifier, 接收方可以知曉哪些消息是邏輯上連續的消息, 在 WebSocket 中, 若不引進額外機制, 則并發傳送時服務端無法區分哪些消息段在邏輯上是同屬于一個消息的, 這里需要通過額外的 WebSocket 擴展機制實現, 此處不深入討論, 下面所討論的分片場景都是在不并發傳送的前提假設下的
消息分片主要利用 frame Header 的 FIN 和 Opcode 字段來實現, 對于未分片的消息, 一個 frame 便承載了完整的消息, 此時它沒有后續的 frame, 因此其 FIN 字段為 1, Opcode 根據該消息是文本消息還是二進制消息分別選擇 0x1 或 0x2, 而對于分片的消息, 我們以文本消息為例, 文本消息的 Opcode 為 0x1, 若不進行分片, 則 frame 的 FIN 字段為 1, 同時 Opcode 字段為 0x1, 若進行分片, 則第一個分片的 frame 的 FIN 字段為 0, Opcode 為 0x1, 但從第二個直到倒數第二個分片, 其 FIN 字段為 0, 并且 Opcode 字段的值為 0x0 (0x0 代表這是一個 continuation frame), 對于最后一個分片的消息, 其 FIN 字段為 1, 并且 Opcode 字段的值為 0x1, 對于分片消息, 發送端必須按序發送, 因此 TCP 保證交付給上層的數據是有序的, 因此接收端也將按發送端發送的順序收到消息, 它可以按序拼接分片得到完整的消息
控制類的 frame (如 Ping frame, Pong frame, 將下面討論) 可以被允許插入在分片消息的發送過程中, 如果不允許, 則對于過長的消息, 其分片數很多, 發送耗時比較長, 控制類的消息需要一直等待消息發送完成而不能及時傳遞給對方, 將會產生一系列問題 (將在下面討論)
6. WebSocket 控制類 frame
控制類 frame 主要用來傳輸一些連接控制信息 (如 Close frame 用來關閉 WebSocket 連接), RFC 6455 總共定義了三種控制類 frame, 分別是 Close frame, Ping frame, Pong frame
Close frame
Close frame, 顧名思義, 用來關閉 WebSocket 連接, 當需要關閉 WebSocket 連接時, 通信方向對方發送 Close frame, frame 可以包含 Payload, 如果包含, 則 Payload 的前兩個字節以小端字節序表示的 16 位整數指示了相應的錯誤碼, 在其后以 ASCII 編碼只是一個錯誤原因, 這個錯誤原因不需要具有可讀性, 一般用來做調試信息用, 當使用 WebSocket 通信的任何一方收到 Close frame 后, 應繼續向對方返回一個 Close frame, 通常需要將錯誤碼回顯給對方, 當接收到 Close frame 并向對方發回 Close frame 后, 通信方便可以認為 WebSocket 連接已關閉, 此時應關閉底層的 TCP 連接
Ping frame
Ping frame 是作為一個探測性的 frame, 主要用來實現 WebSocket 層 Keep-Alive, 或者用來探測對方是否仍然是可回復的狀態, Ping frame 可以包含 Payload
Pong frame
Pong frame 一方面作為 Ping frame 的響應, 接收方接收到 Ping frame 后應立即發回 Pong frame, 并且 Payload 的內容需要和 Ping frame 相同, 若接收方接收到了多個 Ping frame, 還沒來得及回復 Pong frame, 則只需對最后一個 Ping frame 做出回復即可, 另一方面, Pong frame 可以由通信方主動發出, 作為一種心跳包
7. WebSocket 揮手
RFC 6455 將連接關閉表述為 Closing Handshake, 我更傾向于將其表述為揮手, 以便與建立連接的握手區分開, WebSocket 的連接關閉分為 CLOSING 和 CLOSED 兩個階段, 當發送完 Close frame 或接收到對方發來的 Close frame 后, WebSocket 連接便從 OPEN 狀態轉變為 CLOSING 狀態, 此時可以稱揮手已啟動, 通信方接收到 Close frame 后應立即向對方發回 Close frame, 并關閉底層 TCP 連接, 此時 WebSocket 連接處于 CLOSED 狀態
8. WebSocket 狀態碼
與 HTTP 不同, WebSocket 在進行數據傳輸的時候正常情況下都以 frame 為傳輸單元, 不像 HTTP 協議那樣每一次交互都有 Status Code, WebSocket 本身也有狀態碼, 但只用在 Close frame 中, 用于指示連接關閉的原因 (可能是正常關閉也可能是因為發生了錯誤)
RFC 6455 定義了多個 WebSocket 狀態碼:
1000, 代表連接正常關閉
1001, 代表通信方已斷開 (Going AWAY), 例如服務端關機或客戶端關閉網頁
1002, 代表通信方因 protocol error 關閉連接
...