標籤:websocket
參見協議
WebSocket資料幀結構如所示: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+
FIN:1位
表示這是訊息的最後一幀(結束幀),一個訊息由一個或多個資料幀構成。若訊息由一幀構成,起始幀即結束幀。
RSV1,RSV2,RSV3:各1位
這裡我翻譯不好,大致意思是如果未定義擴充,各位是0;如果定義了擴充,即為非0值。如果接收的幀此處非0,擴充中卻沒有該值的定義,那麼關閉串連。
OPCODE:4位
解釋PayloadData,如果接收到未知的opcode,接收端必須關閉串連。
0x0表示附加資料幀
0x1表示文本資料幀
0x2表示位元據幀
0x3-7暫時無定義,為以後的非控制幀保留
0x8表示串連關閉
0x9表示ping
0xA表示pong
0xB-F暫時無定義,為以後的控制幀保留
MASK:1位
用於標識PayloadData是否經過掩碼處理。如果是1,Masking-key域的資料即是掩碼密鑰,用於解碼PayloadData。用戶端發出的資料幀需要進行掩碼處理,所以此位是1。
Payload length:7位,7+16位,7+64位PayloadData的長度(以位元組為單位)。如果其值在0-125,則是payload的真實長度。如果值是126,則後面2個位元組形成的16位無符號整型數的值是payload的真實長度。注意,網路位元組序,需要轉換。如果值是127,則後面8個位元組形成的64位無符號整型數的值是payload的真實長度。注意,網路位元組序,需要轉換。長度表示遵循一個原則,用最少的位元組表示長度(我理解是盡量減少不必要的傳輸)。舉例說,payload真實長度是124,在0-125之間,必須用前7位表示;不允許長度1是126或127,然後長度2是124,這樣違反原則。
以上說明很重要.下面的代碼就是按此原則設計
Payload長度是ExtensionData長度與ApplicationData長度之和。ExtensionData長度可能是0,這種情況下,Payload長度即是ApplicationData長度。
WebSocket協議規定資料通過幀序列傳輸。
用戶端必須對其發送到伺服器的所有幀進行掩碼處理。
伺服器一旦收到無掩碼幀,將關閉串連。伺服器可能發送一個狀態代碼是1002(表示協議錯誤)的Close幀。
而伺服器發送用戶端的資料幀不做掩碼處理,一旦用戶端發現經過掩碼處理的幀,將關閉串連。用戶端可能使用狀態代碼1002。
訊息分區
分區目的是發送長度未知的訊息。如果不分區發送,即一幀,就需要緩衝整個訊息,計算其長度,構建frame並發送;使用分區的話,可使用一個大小合適的buffer,用訊息內容填充buffer,填滿即發送出去。
分區規則:
1.一個未分區的訊息只有一幀(FIN為1,opcode非0)
2.一個分區的訊息由起始幀(FIN為0,opcode非0),若干(0個或多個)幀(FIN為0,opcode為0),結束幀(FIN為1,opcode為0)。
3.控制幀可以出現在分區訊息中間,但控制幀本身不允許分區。
4.分區訊息必須按次序逐幀發送。
5.如果未協商擴充的情況下,兩個分區訊息的幀之間不允許交錯。
6.能夠處理存在於分區訊息幀之間的控制幀
7.發送端為非控制訊息構建長度任意的分區
8.client和server相容接收分區訊息與非分區訊息
9.控制幀不允許分區,中間媒介不允許改變分區結構(即為控制幀分區)
10.如果使用保留位,中間媒介不知道其值表示的含義,那麼中間媒介不允許改變訊息的分區結構
11.如果協商擴充,中間媒介不知道,那麼中間媒介不允許改變訊息的分區結構,同樣地,如果中間媒介不瞭解一個串連的握手資訊,也不允許改變該串連的訊息的分區結構
12.由於上述規則,一個訊息的所有分區是同一資料類型(由第一個分區的opcode定義)的資料。因為控制幀不允許分區,所以一個訊息的所有分區的資料類型是文本、二進位、opcode保留類型中的一種。
需要注意的是,如果控制幀不允許夾雜在一個訊息的分區之間,延遲會較大,比如說當前正在傳輸一個較大的訊息,此時的ping必須等待訊息傳輸完成,才能發送出去,會導致較大的延遲。為了避免類似問題,需要允許控制幀夾雜在訊息分區之間。
部分調試解析訊息頭代碼如下:
#if RFC6455調試代碼 #region 調試代碼 if (receiveBuffer.Length > 2) { byte bt1 = receiveBuffer[0]; byte bt2 = receiveBuffer[1]; int Opcode = ((bt1 >> 4) & 0xF); Console.WriteLine(string.Format("FIN:{0},RSV1:{1} RSV2:{2} RSV3:{3};Opcode:{4},mask:{5};len:{6}" , bt1 >> 7 & 1 , (bt1 >> 6) & 1 , (bt1 >> 5) & 1 , (bt1 >> 4) & 1 , (bt1 & 0xF).ToString("X2") ///////第二位元組 , bt2 >> 7 & 1 //mask , bt2 & 0x7f //Payload len ==((byte)(bt2<<1)>>1) )); //FIN:1位,用來表明這是一個訊息的最後的訊息片斷,當然第一個訊息片斷也可能是最後的一個訊息片斷 //RSV1, RSV2, RSV3: 分別都是1位,如果雙方之間沒有約定自訂協議,那麼這幾位的值都必須為0,否則必須斷掉WebSocket串連 //Opcode:4位作業碼,定義承載資料,如果收到了一個未知的作業碼,串連也必須斷掉,以下是定義的作業碼: //* %x0 表示連續訊息片斷 //* %x1 表示簡訊片斷 //* %x2 表未二進位訊息片斷 //* %x3-7 為將來的非控制訊息片斷保留的作業碼 //* %x8 表示串連關閉 //* %x9 表示心跳檢查的ping //* %xA 表示心跳檢查的pong //* %xB-F 為將來的控制訊息片斷的保留作業碼 } #endregion#endif
發送方法代碼如下:
/// <summary> /// WebSocket Send 發送資料到用戶端,打包伺服器資料 /// </summary> /// <param name="bytes">要發送的資料</param> /// <param name="sendMax">每次發送的最大資料包</param> public void Send(Byte[] bytes,int sendMax=65536) { bool canSend = true; //每次最大發送 64Kb的資料 int SendMax = sendMax; int num = 0; //已經發送的位元組資料 int taked = 0; while (canSend) { //內容資料 byte[] contentBytes = null; var sendArr = bytes.Skip(num * SendMax).Take(SendMax).ToArray(); taked += sendArr.Length; if (sendArr.Length > 0) { //是否可以繼續發送 canSend = bytes.Length > taked; if (sendArr.Length < 126) { #region 一次發送小於126的資料 contentBytes = new byte[sendArr.Length + 2]; contentBytes[0] = (byte)(num == 0 ? 0x81 : (!canSend ? 0x80 : 0)); contentBytes[1] = (byte)sendArr.Length; Array.Copy(sendArr, 0, contentBytes, 2, sendArr.Length); canSend = false; #endregion } else if (sendArr.Length < 0xFFFF) { #region 發送小於65535的資料 contentBytes = new byte[sendArr.Length + 4]; //首次不分區發送,大於128位元組的資料一次發完 if (!canSend && num == 0) { contentBytes[0] = 0x81; } else { //一個分區的訊息由起始幀(FIN為0,opcode非0),若干(0個或多個)幀(FIN為0,opcode為0),結束幀(FIN為1,opcode為0)。 contentBytes[0] = (byte)(num == 0 ? 0x01 : (!canSend ? 0x80 : 0)); } contentBytes[1] = 126; byte[] ushortlen = BitConverter.GetBytes((short)sendArr.Length); contentBytes[2] = ushortlen[1]; contentBytes[3] = ushortlen[0]; Array.Copy(sendArr, 0, contentBytes, 4, sendArr.Length); #endregion } else if (sendArr.LongLength < long.MaxValue) { #region 一次發送所有資料 //long資料一次發完 contentBytes = new byte[sendArr.Length + 10]; //首次不分區發送,大於128位元組的資料一次發完 if (!canSend && num == 0) { contentBytes[0] = 0x81; } else { //一個分區的訊息由起始幀(FIN為0,opcode非0),若干(0個或多個)幀(FIN為0,opcode為0),結束幀(FIN為1,opcode為0)。 contentBytes[0] = (byte)(num == 0 ? 0x01 : (!canSend ? 0x80 : 0)); } contentBytes[1] = 127; byte[] ulonglen = BitConverter.GetBytes((long)sendArr.Length); contentBytes[2] = ulonglen[7]; contentBytes[3] = ulonglen[6]; contentBytes[4] = ulonglen[5]; contentBytes[5] = ulonglen[4]; contentBytes[6] = ulonglen[3]; contentBytes[7] = ulonglen[2]; contentBytes[8] = ulonglen[1]; contentBytes[9] = ulonglen[0]; Array.Copy(sendArr, 0, contentBytes, 10, sendArr.Length); #endregion } } try { if (contentBytes != null) { Socket.Send(contentBytes); } } catch (NullReferenceException) { break; } catch (System.Net.Sockets.SocketException) { break; } catch (ObjectDisposedException) { break; } finally { num++; } } }
WebSocket C# 服務端發送大資料,分包發送大資料 方法