1. 程式人生 > >WebSocket資料包協議詳解

WebSocket資料包協議詳解

WebSocket協議主要分為兩部分,第一部分是連線許可驗證和驗證後的資料互動.連線許可驗證比較簡單,由Client傳送一個類似於HTTP的請求,服務端獲取請求後根據請求的KEY生成對應的值並返回.

連線請求內容:

12345678GET / HTTP/1.1Connection:UpgradeHost:127.0.0.1:8088Origin:nullSec-WebSocket-Extensions:x-webkit-deflate-frameSec-WebSocket-Key:puVOuWb7rel6z2AVZBKnfw==Sec-WebSocket-Version:13Upgrade:websocket

服務端接收請求後主要是成針對Sec-WebSocket-Key生成對就Sec-WebSocket-Accept 的key,生成Sec-WebSocket-Accept 值比較簡單就是Sha1(Sec-WebSocket-Key+258EAFA5-E914-47DA-95CA-C5AB0DC85B11)即可,C#程式碼如下:

12345SHA1 sha1 = new SHA1CryptoServiceProvider();byte[] bytes_sha1_in = Encoding.UTF8.GetBytes(request.SecWebSocketKey+ "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
);byte[] bytes_sha1_out = sha1.ComputeHash(bytes_sha1_in);string str_sha1_out = Convert.ToBase64String(bytes_sha1_out);response.SecWebSocketAccept = str_sha1_out;

服務端返回內容:

12345678HTTP/1.1 101 Switching ProtocolsConnection:UpgradeServer:beetle websocket serverUpgrade:WebSocketDate:Mon, 26 Nov 2012 23:42:44 GMT
Access-Control-Allow-Credentials:trueAccess-Control-Allow-Headers:content-typeSec-WebSocket-Accept:FCKgUr8c7OsDsLFeJTWrJw6WO8Q= 

經過伺服器的返回處理後連線握手成功,後面就可以進行TCP通訊.WebSocket在握手後傳送資料並象下層TCP協議那樣由使用者自定義,還是需要遵循對應的應用協議規範...這也是在文章之說沒有直接基於Socket tcp方便的原因. 

 資料互動協議:

這圖有點難看懂...裡面包括幾種情況有掩碼,資料長度小於126,小於UINT16和小於UINT64等幾種情況.後面會慢慢詳細說明.整個協議頭大概分三部分組成,第一部分是描述訊息結束情況和型別,第二部分是描述是否存在掩碼長度,第三部分是擴充套件長度描述和掩碼值.

從圖中可以看到WebSocket協議資料主要通過頭兩個位元組來描述資料包的情況

第一個位元組

最高位用於描述訊息是否結束,如果為1則該訊息為訊息尾部,如果為零則還有後續資料包;後面3位是用於擴充套件定義的,如果沒有擴充套件約定的情況則必須為0.可以通過以下c#程式碼方式得到相應值

1mDataPackage.IsEof = (data[start] >> 7) > 0;

最低4位用於描述訊息型別,訊息型別暫定有15種,其中有幾種是預留設定.c#程式碼可以這樣得到訊息型別:

12int type = data[start] & 0xF;mDataPackage.Type = (PackageType)type;

第二個位元組

訊息的第二個位元組主要用一描述掩碼和訊息長度,最高位用0或1來描述是否有掩碼處理,可以通過以下c#程式碼方式得到相應值

1bool hasMask = (data[start] >>7) > 0;

剩下的後面7位用來描述訊息長度,由於7位最多隻能描述127所以這個值會代表三種情況,一種是訊息內容少於126儲存訊息長度,如果訊息長度少於UINT16的情況此值為126,當訊息長度大於UINT16的情況下此值為127;這兩種情況的訊息長度儲存到緊隨後面的byte[],分別是UINT16(2位byte)和UINT64(4位byte).可以通過以下c#程式碼方式得到相應值

123456789101112mPackageLength = (uint)(data[start] & 0x7F);start++;if (mPackageLength == 126){mPackageLength = BitConverter.ToUInt16(data, start);start = start + 2;}else if (mPackageLength == 127){mPackageLength = BitConverter.ToUInt64(data, start);start = start + 8;}

如果存在掩碼的情況下獲取4位掩碼值:

12345678if (hasMask){mDataPackage.Masking_key = new byte[4];Buffer.BlockCopy(data, start, mDataPackage.Masking_key, 0, 4);start = start + 4;count = count - 4;}

獲取訊息體

當得到訊息體長度後就可以獲取對應長度的byte[],有些訊息型別是沒有長度的如%x8 denotes a connection close.對於Text型別的訊息對應的byte[]是相應字元的UTF8編碼.獲取訊息體還有一個需要注意的地方就是掩碼,如果存在掩碼的情況下接收的byte[]要做如下轉換處理:

123456if (mDataPackage.Masking_key != null){int length = mDataPackage.Data.Count;for (var i = 0; i < length; i++)mDataPackage.Data.Array[i] = (byte)(mDataPackage.Data.Array[i] ^ mDataPackage.Masking_key[i % 4]);}