BeetleX之Websocket协议分析详解
??????? Websocket應用協議已經普及多年了,它是HTTP1.1的內部升級協議,主要作用是補充HTTP1.1無法靈活地主動推送消息給客戶端的缺陷問題。在這里主要介紹一下使用組件如何擴展一個完整的Websocket協議。
協議介紹
????????Websocket并不復雜,但協議文檔內容還是很全面的,以下是協議原文
https://tools.ietf.org/html/rfc6455。其實一個簡單的圖可以看出Websocket協議結構。
在這里主要介紹組件是如何實現的就不詳細介紹內容了。
存儲順序
????????在協議中有一個地方需要關注存儲順序,那就是消息長度描述。不同語言平臺對于基礎值類型的存儲順序都不一樣分別是:大端和小端。這個協議使用的是大端存儲順序,但.NET則是使用小端存儲順序;所以使用組件解Weboskcet協議前要更改一下流讀寫的存儲順序。
組件可以通過配置來統一更改網絡流針對大小端讀寫配置,應用中也可以默認用小端讀出來后再移位轉換也是可以。
分析狀態
????????雖然Websocket已經有協議描述,但在分析過程中還是需要一些狀態來處理。在TCP流中無法知道當前buffer里的情況,有可能不到一個消息幀,或存在多個消息幀;更有可能當前流的尾部可能只兩個字節內容的playload len 127的情況;為了應對存在不同狀態的網絡流,在分析協議過程需要制定各種狀態,以便于下一次網絡數據到來直接跑到相關狀態分配處理。
握手處理
????????其實Websocket設計作為http 1.1的一個升級協議,所以在連接開始是通過http協議作為應用握手確認;確認后雙方即可隨意發送基于websocket協議描述的幀數據。
????????當服務端收到HTTP請求存在Upgrade頭部信息的內容是Websocket的情況說明客戶端要求升級到Websocket協議。
GET /chat HTTP/1.1Host: server.example.comUpgrade: websocketConnection: UpgradeSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Origin: http://example.comSec-WebSocket-Protocol: chat, superchatSec-WebSocket-Version: 13如果接受升級,服務端響應相關內容即可
組件FastHttpApi對應代碼
https://github.com/IKende/FastHttpApi/blob/master/src/HttpApiServer.cs#L691
數據幀解包
??????? WebSocket的數據幀解釋比起http協議麻煩些,畢竟http協議都是換行拆分即可;而WebSocket則需要涉及到位信息處理。
internal DataPacketLoadStep Read(PipeStream stream){if (mLoadStep == DataPacketLoadStep.None){//當前流是否滿足解釋頭兩個字節需求if (stream.Length >= 2){byte value = (byte)stream.ReadByte();this.FIN = (value & CHECK_B8) > 0;this.RSV1 = (value & CHECK_B7) > 0;this.RSV2 = (value & CHECK_B6) > 0;this.RSV3 = (value & CHECK_B5) > 0;this.Type = (DataPacketType)(byte)(value & 0xF);value = (byte)stream.ReadByte();this.IsMask = (value & CHECK_B8) > 0;this.PayloadLen = (byte)(value & 0x7F);mLoadStep = DataPacketLoadStep.Header;}}if (mLoadStep == DataPacketLoadStep.Header){//是否滿足解釋幀長度需求if (this.PayloadLen == 127){if (stream.Length >= 8){Length = stream.ReadUInt64();mLoadStep = DataPacketLoadStep.Length;}}else if (this.PayloadLen == 126){if (stream.Length >= 2){Length = stream.ReadUInt16();mLoadStep = DataPacketLoadStep.Length;}}else{this.Length = this.PayloadLen;mLoadStep = DataPacketLoadStep.Length;}}if (mLoadStep == DataPacketLoadStep.Length){if (IsMask){if (stream.Length >= 4){this.MaskKey = new byte[4];stream.Read(this.MaskKey, 0, 4);mLoadStep = DataPacketLoadStep.Mask;}}else{mLoadStep = DataPacketLoadStep.Mask;}}if (mLoadStep == DataPacketLoadStep.Mask){//根據不同長度判斷可讀開度內容if (this.Length == 0){mLoadStep = DataPacketLoadStep.Completed;}else{if ((ulong)stream.Length >= this.Length){if (this.IsMask)ReadMask(stream);Body = this.DataPacketSerializer.FrameDeserialize(this, stream);mLoadStep = DataPacketLoadStep.Completed;}}}return mLoadStep;}看完以上代碼相信會有人問,寫這么復雜干什么嗎,幾個字節的長度都需要判斷嗎?一次接收的信息不可能幾個字節都沒有。出現這情況的主要原因是當某端推送大量的消息,這些消息經過不同的網絡環境和MTU限制后,可能出現幀的頭部內容被拆到兩個接收緩沖區中,所以在處理上需要完全考慮這種情況。
數據幀封包?
void IDataResponse.Write(PipeStream stream) {byte[] header = new byte[2];if (FIN)header[0] |= CHECK_B8;if (RSV1)header[0] |= CHECK_B7;if (RSV2)header[0] |= CHECK_B6;if (RSV3)header[0] |= CHECK_B5;header[0] |= (byte)Type;if (Body != null){ArraySegment<byte> data = this.DataPacketSerializer.FrameSerialize(this, Body);try{if (MaskKey == null || MaskKey.Length != 4)this.IsMask = false;//是否有掩碼if (this.IsMask){header[1] |= CHECK_B8;int offset = data.Offset;for (int i = offset; i < data.Count; i++){data.Array[i] = (byte)(data.Array[i] ^ MaskKey[(i - offset) % 4]);}}int len = data.Count;//大于135小于unit16長度的消息頭寫入if (len > 125 && len <= UInt16.MaxValue){header[1] |= (byte)126;stream.Write(header, 0, 2);stream.Write((UInt16)len);}//大于unit16長度頭寫入else if (len > UInt16.MaxValue){header[1] |= (byte)127;stream.Write(header, 0, 2);stream.Write((ulong)len);}else{//小于126長度寫入header[1] |= (byte)data.Count;stream.Write(header, 0, 2);}//寫入掩碼if (IsMask)stream.Write(MaskKey, 0, 4);//寫入消息內容stream.Write(data.Array, data.Offset, data.Count);}finally{this.DataPacketSerializer.FrameRecovery(data.Array);}}else{//沒有消息體,只寫入消息頭stream.Write(header, 0, 2);} }封包就簡單了,除了判斷長度寫入不同的頭信息外其他都是直接寫入。以上代碼可以查看
https://github.com/IKende/FastHttpApi/blob/master/src/WebSockets/DataFrame.cs
【BeetleX通訊框架代碼詳解】 BeetleX開源跨平臺通訊框架(支持TLS)
輕松實現高性能:tcp、http、websocket、redis、rpc和網關等服務應用
https://beetlex.io
如果你想了解某方面的知識或文章可以把想法發送到
henryfan@msn.com|admin@beetlex.io
總結
以上是生活随笔為你收集整理的BeetleX之Websocket协议分析详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET 5.0 RC 2 发布,正式版
- 下一篇: 甲骨文是否可以要求 Java API 享