基于.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());

......

至此,我们终于可以在客户端和服务器之间进行有效沟通了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值