WebSocket C# 服務端發送大資料,分包發送大資料 方法

來源:互聯網
上載者:User

標籤: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# 服務端發送大資料,分包發送大資料 方法

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.