基于.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,  02);
        }
         else  if (_header.Length ==  127)
        {
            _extend =  new  byte[ 8];
            Buffer.BlockCopy(buffer,  2, _extend,  08);
        }
         // 是否有掩码
         if (_header.HasMask)
        {
            _mask =  new  byte[ 4];
            Buffer.BlockCopy(buffer, _extend.Length +  2, _mask,  04);
        }            
         // 消息体
         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,  28))];
            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( truefalsefalsefalse, OpCode.Text,  false, length);
        }
         else  if (length <  65536)
        {
            _extend =  new  byte[ 2];
            _header =  new DataFrameHeader( truefalsefalsefalse, OpCode.Text,  false126);
            _extend[ 0] = ( byte)(length /  256);
            _extend[ 1] = ( byte)(length %  256);
        }
         else
        {
            _extend =  new  byte[ 8];
            _header =  new DataFrameHeader( truefalsefalsefalse, OpCode.Text,  false127);

             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,  02);
        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());

......
复制代码

 

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值