日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

BeetleX之Websocket协议分析详解

發布時間:2023/12/4 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 BeetleX之Websocket协议分析详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

??????? Websocket應用協議已經普及多年了,它是HTTP1.1的內部升級協議,主要作用是補充HTTP1.1無法靈活地主動推送消息給客戶端的缺陷問題。在這里主要介紹一下使用組件如何擴展一個完整的Websocket協議。

協議介紹

????????Websocket并不復雜,但協議文檔內容還是很全面的,以下是協議原文

https://tools.ietf.org/html/rfc6455。其實一個簡單的圖可以看出Websocket協議結構。

在這里主要介紹組件是如何實現的就不詳細介紹內容了。

存儲順序

????????在協議中有一個地方需要關注存儲順序,那就是消息長度描述。不同語言平臺對于基礎值類型的存儲順序都不一樣分別是:大端和小端。這個協議使用的是大端存儲順序,但.NET則是使用小端存儲順序;所以使用組件解Weboskcet協議前要更改一下流讀寫的存儲順序。

IServer.Options.LittleEndian?=?false;

組件可以通過配置來統一更改網絡流針對大小端讀寫配置,應用中也可以默認用小端讀出來后再移位轉換也是可以。

分析狀態

????????雖然Websocket已經有協議描述,但在分析過程中還是需要一些狀態來處理。在TCP流中無法知道當前buffer里的情況,有可能不到一個消息幀,或存在多個消息幀;更有可能當前流的尾部可能只兩個字節內容的playload len 127的情況;為了應對存在不同狀態的網絡流,在分析協議過程需要制定各種狀態,以便于下一次網絡數據到來直接跑到相關狀態分配處理。

public enum DataPacketLoadStep {//量開始狀態None,//分析完頭部信息Header,//分析完成內容長度信息Length,//內容在校檢狀態Mask,//分析完成Completed }

握手處理

????????其實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

如果接受升級,服務端響應相關內容即可

HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

組件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协议分析详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。