基於.NET 的WebSocket 的簡單實例 --- 數據格式

上一篇我們已經在服務器和客戶端之間建立起一個能雙向通訊的途徑,如果你馬上按以前的經驗直接丟送數據,恭喜,數據能過去,可你卻根本不認識,這是自然,他的地盤他要做主,websocket 有其自己約定的數據格式,我們必須按照這個格式來才行的。

協議這玩意,很是枯燥,只能用,不能創新,但多瞭解一些這種規則,對以後我們定義傳輸協議有很強的參考價值,所以我們還是得認真瞧瞧。

找到如下圖所示的位置,這裏面有詳細的說明

具體每個字段的說明,大家可以慢慢啃英文,我們就討論一下,如何將這玩意,在我們的系統中使用吧。

先用我的語言理解一下這個格式,有什麼不正確歡迎大家指正。
這個格式中,最重要的就是第二個字節:

1.第一位決定是否有掩碼,如果Mask爲1,就會有後面那個4字節的Masking-Key,客戶發送過來的數據,都有個這值,所以當我嘗試發送一個空串過來的時候,服務器會收到類似如下的數據:(以2進制顯示,方便大家瞭解)。


傳輸的數據,採用掩碼的異或運算來產生,比如我發送一個字符1,服務器會收到如下數據:
1000 0001 1000 0001 0011 1100 1001 0111 0011 1001 1010 1111 0000 1101
如果Mask爲0,則不會有後面的掩碼

2.後7位是用來決定這個幀的長度,重點爲原文中的這一句話:
Payload length:  7 bits, 7+16 bits, or 7+64 bits
如果這7位表示的長度小於126,則此即發送數據的實際長度
如果等於126,則後面兩個字節表示長度,即7+16Bits的意思
如果等於127,則後面的8字節表示長度,即7+64Bits的意思
數據傳輸協議,就應該合理的使用每一個數據位,雖然感覺理解累些,但卻能減少數據傳輸的數量,這就我們大設計協議的時候,應該好好學習的。

爲此,我封裝瞭如下兩個類,
DataFrameHeader,對協議中頭兩個字節的處理,代碼如下:

/// <summary>
/// 2字節數據頭
/// </summary>
public class DataFrameHeader
{
    private bool _fin;
    private bool _rsv1;
    private bool _rsv2;
    private bool _rsv3;
    private sbyte _opcode;
    private bool _maskcode;
    private sbyte _payloadlength;

    /// <summary>
   
/// FIN
   
/// </summary>
    public bool FIN { get { return _fin; } }

    /// <summary>
   
/// RSV1
   
/// </summary>
    public bool RSV1 { get { return _rsv1; } }

    /// <summary>
   
/// RSV2
   
/// </summary>
    public bool RSV2 { get { return _rsv2; } }

    /// <summary>
   
/// RSV3
   
/// </summary>
    public bool RSV3 { get { return _rsv3; } }

    /// <summary>
   
/// OpCode
   
/// </summary>
    public sbyte OpCode { get { return _opcode; } }

    /// <summary>
   
/// 是否有掩碼
   
/// </summary>
    public bool HasMask { get { return _maskcode; } }

    /// <summary>
   
/// Payload Length
   
/// </summary>
    public sbyte Length { get { return _payloadlength; } }

    /// <summary>
   
/// 構造函數
   
/// </summary>
   
/// <remarks>主要用於解析接收數據</remarks>
    public DataFrameHeader(byte[] buffer)
    {
        if(buffer.Length<2)
            throw new Exception("無效的數據頭.");
        //第一個字節
        _fin = (buffer[0] & 0x80) == 0x80;
        _rsv1 = (buffer[0] & 0x40) == 0x40;
        _rsv2 = (buffer[0] & 0x20) == 0x20;
        _rsv3 = (buffer[0] & 0x10) == 0x10;
        _opcode = (sbyte)(buffer[0] & 0x0f);
        //第二個字節
        _maskcode = (buffer[1] & 0x80) == 0x80;
        _payloadlength = (sbyte)(buffer[1] & 0x7f);

    }

    /// <summary>
   
/// 構造函數
   
/// </summary>
   
/// <remarks>主要用於發送封裝數據</remarks>
    public DataFrameHeader(bool fin,bool rsv1,bool rsv2,bool rsv3,sbyte opcode,bool hasmask,int length)
    {
        _fin = fin;
        _rsv1 = rsv1;
        _rsv2 = rsv2;
        _rsv3 = rsv3;
        _opcode = opcode;
        //第二個字節
        _maskcode = hasmask;
        _payloadlength = (sbyte)length;
    }

    /// <summary>
   
/// 返回幀頭字節
   
/// </summary>
   
/// <returns></returns>
    public byte[] GetBytes()
    {
        byte[] buffer = new byte[2]{0,0};

        if (_fin) buffer[0] ^= 0x80;
        if (_rsv1) buffer[0] ^= 0x40;
        if (_rsv2) buffer[0] ^= 0x20;
        if (_rsv3) buffer[0] ^= 0x10;
        buffer[0] ^= (byte)_opcode;

        if (_maskcode) buffer[1] ^= 0x80;
        buffer[1] ^= (byte)_payloadlength;

        return buffer;
    }
}

DataFrame,對協議整體的封裝,代碼如下:

/// <summary>
/// 數據幀
/// </summary>
public class DataFrame
{
    DataFrameHeader _header;
    private byte[] _extend = new byte[0];
    private byte[] _mask = new byte[0];
    private byte[] _content = new byte[0];

    /// <summary>
   
/// 構造函數
   
/// </summary>
   
/// <remarks>主要用於解析接收數據</remarks>
    public DataFrame(byte[] buffer)
    {
        //格式化幀頭
        _header = new DataFrameHeader(buffer);
        //填充擴展長度字節
        if (_header.Length == 126)
        {
            _extend = new byte[2];
            Buffer.BlockCopy(buffer, 2, _extend, 0, 2);
        }
        else if (_header.Length == 127)
        {
            _extend = new byte[8];
            Buffer.BlockCopy(buffer, 2, _extend, 0, 8);
        }
        //是否有掩碼
        if (_header.HasMask)
        {
            _mask = new byte[4];
            Buffer.BlockCopy(buffer, _extend.Length + 2, _mask, 0, 4);
        }           
        //消息體
        if (_extend.Length == 0)
        {
            _content = new byte[_header.Length];
            Buffer.BlockCopy(buffer, _extend.Length + _mask.Length + 2 , _content, 0, _content.Length);
        }
        else if (_extend.Length == 2)
        {
            _content = new byte[Convert.ToUInt16(_extend)];
            Buffer.BlockCopy(buffer, _extend.Length + _mask.Length + 2, _content, 0, _content.Length);
        }
        else
        {
            _content = new byte[Convert.ToUInt64(Common.CopyArrayData(buffer, 2, 8))];
            Buffer.BlockCopy(buffer, _extend.Length + _mask.Length + 2, _content, 0, _content.Length);
        }
        //如果有掩碼,則需要還原原始數據
        if (_header.HasMask) _content = Mask(_content, _mask);

    }

    /// <summary>
   
/// 構造函數
   
/// </summary>
   
/// <remarks>主要用於發送封裝數據</remarks>
    public DataFrame(string content)
    {
        _content = Encoding.UTF8.GetBytes(content);
        int length = _content.Length;
           
        if (length < 126)
        {
            _extend = new byte[0];
            _header = new DataFrameHeader(true, false, false, false, OpCode.Text, false, length);
        }
        else if (length < 65536)
        {
            _extend = new byte[2];
            _header = new DataFrameHeader(true, false, false, false, OpCode.Text, false, 126);
            _extend[0] = (byte)(length / 256);
            _extend[1] = (byte)(length % 256);
        }
        else
        {
            _extend = new byte[8];
            _header = new DataFrameHeader(true, false, false, false, OpCode.Text, false, 127);

            int left = length;
            int unit = 256;

            for (int i = 7; i > 1; i--)
            {
                _extend[i] = (byte)(left % unit);
                left = left / unit;

                if (left == 0)
                    break;
            }
        }
    }

    /// <summary>
   
/// 獲取適合傳送的字節數據
   
/// </summary>
    public byte[] GetBytes()
    {
        byte[] buffer = new byte[2 + _extend.Length + _mask.Length + _content.Length];
        Buffer.BlockCopy(_header.GetBytes(), 0, buffer, 0, 2);
        Buffer.BlockCopy(_extend, 0, buffer, 2, _extend.Length);
        Buffer.BlockCopy(_mask, 0, buffer, 2 + _extend.Length, _mask.Length);
        Buffer.BlockCopy(_content, 0, buffer, 2 + _extend.Length + _mask.Length, _content.Length);
        return buffer;
    }
       
    /// <summary>
   
/// 獲取文本
   
/// </summary>
    public string Text
    {
        get
        {
            if (_header.OpCode != OpCode.Text)
                return string.Empty;

            return Encoding.UTF8.GetString(_content);
        }
    }

    /// <summary>
   
/// 加掩碼運算
   
/// </summary>
    private byte[] Mask(byte[] data, byte[] mask)
    {
        for (var i = 0; i < data.Length; i++)
        {
            data[i] = (byte)(data[i] ^ mask[i % 4]);
        }

        return data;
    }

}

調用的關鍵代碼如下:

......


//RecvDataBuffer是接收到的數據字節數組,這段代碼即服務端收到客戶端的信息,加個時間和Hello,再返回給客戶。
DataFrame dr = new DataFrame(RecvDataBuffer);

string strResp = String.Format("[{0}]:Hello,{1}.",DateTime.Now.ToString(), dr.Text);

dr = new DataFrame(strResp);

client.Send(dr.GetBytes());

......

至此,我們終於可以在客戶端和服務器之間進行有效溝通了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章