zoukankan      html  css  js  c++  java
  • C#封装的websocket协议类

    关于VB版之前已经写了,有需要可以进传送门《VB封装的WebSocket模块,拿来即用》两个使用都差不多,这里简单概述一下:

    连接完成后,没有握手就用Handshake()先完成握手
    之后接收数据,先用AnalyzeHeader()得到数据帧结构(DataFrame)
    然后再用PickDataV()PickData()得到源数据,对于掩码数据是在这里反掩码
    关于发送数据,则是:
    服务端发送无需掩码用PackData()将数据组装一下就可以发送
    而模拟客户端向服务器的发送需要加掩码,用PackMaskData()

    相关资料下载:《WebSocket协议中文版.pdf》
    *等有时间我再做个demo供下载吧,这个类使用还算简单

    WebSocketProtocol10.cs

      1 using System;
      2 using System.Collections;
      3 using System.Collections.Generic;
      4 using System.Collections.Specialized;
      5 using System.Globalization;
      6 using System.Runtime.InteropServices;
      7 using System.Security.Cryptography;
      8 using System.Text;
      9 /*
     10  *  详见<5.2 基本帧协议>和<11.8.WebSocket 操作码注册>
     11     0                   1                   2                   3
     12     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
     13     +-+-+-+-+-------+-+-------------+-------------------------------+
     14     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
     15     |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
     16     |N|V|V|V|       |S|             |   (if payload len==126/127)   |
     17     | |1|2|3|       |K|             |                               |
     18     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     19     |     Extended payload length continued, if payload len == 127  |
     20     + - - - - - - - - - - - - - - - +-------------------------------+
     21     |                               |Masking-key, if MASK set to 1  |
     22     +-------------------------------+-------------------------------+
     23     | Masking-key (continued)       |          Payload Data         |
     24     +-------------------------------- - - - - - - - - - - - - - - - +
     25     :                     Payload Data continued ...                :
     26     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
     27     |                     Payload Data continued ...                |
     28     +---------------------------------------------------------------+
     29 */
     30 //2017-06-17 
     31 //By:       悠悠然
     32 //QQ:       2860898817
     33 //E-mail:   ur1986@foxmail.com
     34 namespace WebSocketProtocol10
     35 {
     36     /// <summary>
     37     /// Opcode操作码是一个在 0 到 15(包括)之间的整数数字。
     38     /// </summary>
     39     public enum OpcodeType:byte
     40     {
     41         Contin = 0x0,   //表示连续消息片断
     42         Text = 0x1,     //表示文本消息片断
     43         Binary = 0x2,   //表未二进制消息片断
     44         // 0x3 - 0x7 非控制帧保留
     45         Close = 0x8,    //表示连接关闭
     46         Ping = 0x9,     //表示心跳检查的ping
     47         Pong = 0xA,     //表示心跳检查的pong
     48         // 0xB - 0xF 控制帧保留
     49         Unkown
     50     };
     51     /// <summary>
     52     /// 数据帧头,就是包头结构
     53     /// </summary>
     54     public struct DataFrame
     55     {
     56         /// <summary>0表示不是当前消息的最后一帧,后面还有消息,1表示这是当前消息的最后一帧;</summary>
     57         public bool FIN;
     58         /// <summary>1位,若没有自定义协议,必须为0,否则必须断开.</summary>
     59         public bool RSV1;
     60         /// <summary>1位,若没有自定义协议,必须为0,否则必须断开.</summary>
     61         public bool RSV2;
     62         /// <summary>1位,若没有自定义协议,必须为0,否则必须断开.</summary>
     63         public bool RSV3;
     64         /// <summary>4位操作码,定义有效负载数据,如果收到了一个未知的操作码,连接必须断开.</summary>
     65         public OpcodeType Opcode;
     66         /// <summary>1位,定义传输的数据是否有加掩码,如果有掩码则存放在MaskingKey</summary>
     67         public bool MASK;
     68         /// <summary>0或4个字节,客户端发送给服务端的数据,都是通过内嵌的一个32位值作为掩码的;掩码键只有在掩码位设置为1的时候存在。</summary>
     69         [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
     70         public byte[] MaskingKey;
     71         /// <summary>传输数据的长度</summary>
     72         public int Payloadlen;
     73         /// <summary>数据起始位</summary>
     74         public int DataOffset;
     75     }
     76 
     77     public class WSProtocol
     78     {
     79         #region 握手
     80         /// <summary>
     81         /// 获取连接请求附带的参数
     82         /// </summary>
     83         public static NameValueCollection QueryString(byte[] recv)
     84         {
     85             //前端js如:  ws = new WebSocket("ws://127.0.0.1:8899/ws?id=1&session=a1b2c3")
     86             //该函数相当于ASP.NET中的Request.QueryString,就是取得参数 id 和 session 的
     87             NameValueCollection NV = new NameValueCollection();
     88             string n = string.Empty;
     89             string v = string.Empty;
     90             bool tf1 = false;
     91             bool tf2 = false;
     92             foreach (byte b in recv)
     93             {
     94                 if (tf1)
     95                 {
     96                     if (b == 32)
     97                         break;
     98                     else if (b == 61 && tf2 == false)//=
     99                         tf2 = true;
    100                     else if (b == 38)//&
    101                     {
    102                         tf2 = false;
    103                         if (!string.IsNullOrEmpty(n) && !string.IsNullOrEmpty(v))
    104                             NV.Add(n, v);
    105                         n = string.Empty;
    106                         v = string.Empty;
    107                     }
    108                     else
    109                     {
    110                         if (tf2)
    111                             v += (char)b;
    112                         else
    113                             n += (char)b;
    114                     }
    115                 }
    116                 else if (b == 63)//?
    117                     tf1 = true;
    118                 else if (b == 10 || b == 13) break;
    119             }
    120             if (!string.IsNullOrEmpty(n) && !string.IsNullOrEmpty(v))
    121                 NV.Add(n, v);
    122             return NV;
    123         }
    124         public static byte[] Handshake(string request)
    125         {
    126             string webSocketKey = getCilentWSKey(request, "Sec-WebSocket-Key:");
    127             string acceptKey = produceAcceptKey(webSocketKey);
    128             StringBuilder response = new StringBuilder(); //响应串
    129             response.Append("HTTP/1.1 101 Web Socket Protocol Handshake
    ");
    130             response.Append("Upgrade: WebSocket
    ");
    131             response.Append("Connection: Upgrade
    ");
    132             response.AppendFormat("Sec-WebSocket-Accept: {0}
    ", acceptKey);
    133             response.AppendFormat("WebSocket-Origin: {0}
    ", getCilentWSKey(request, "Sec-WebSocket-Origin"));
    134             response.AppendFormat("WebSocket-Location: {0}
    ", getCilentWSKey(request, "Host"));
    135             response.Append("
    ");
    136             return Encoding.UTF8.GetBytes(response.ToString());
    137         }
    138         private static string getCilentWSKey(string request, string kname)
    139         {
    140             int i = CultureInfo.InvariantCulture.CompareInfo.IndexOf(request, kname, CompareOptions.IgnoreCase);
    141             if (i > 0)
    142             {
    143                 i += kname.Length;
    144                 int j = request.IndexOf("
    ", i);
    145                 if (j > 0)
    146                     return request.Substring(i, j - i).Trim();
    147             }
    148             return string.Empty;
    149         }
    150         // 根据Sec-WebSocket-Key和MagicKey生成AcceptKey
    151         private static string produceAcceptKey(string webSocketKey)
    152         {
    153             string MagicKey = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    154             Byte[] acceptKey = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(webSocketKey + MagicKey));
    155             return Convert.ToBase64String(acceptKey);
    156         }
    157         #endregion
    158         #region 数据帧解析
    159         /// <summary>
    160         /// 数据帧头的解析
    161         /// </summary>
    162         public static DataFrame AnalyzeHeader(byte[] data)
    163         {
    164             DataFrame df;
    165             df.FIN = (data[0] & 0x80) == 0x80 ? true : false;
    166             df.RSV1 = (data[0] & 0x40) == 0x40 ? true : false;
    167             df.RSV2 = (data[0] & 0x40) == 0x20 ? true : false;
    168             df.RSV3 = (data[0] & 0x40) == 0x10 ? true : false;
    169             byte[] b = { data[0] };
    170             BitArray bit = new BitArray(b);
    171             bit.Set(4, false);
    172             bit.Set(5, false);
    173             bit.Set(6, false);
    174             bit.Set(7, false);
    175             bit.CopyTo(b, 0);
    176             df.Opcode = (OpcodeType)b[0];
    177 
    178             df.MASK = (data[1] & 0x80) == 0x80 ? true : false;
    179             df.MaskingKey = new Byte[4];
    180             int len = data[1] & 0x7F;
    181             /// 0-125 表示传输数据的长度;
    182             /// 126   表示随后的两个字节是一个16进制无符号数,用来表示传输数据的长度;
    183             /// 127   表示随后的是8个字节是一个64位无符合数,这个数用来表示传输数据的长度。
    184             /// 多字节长度的数量是以网络字节的顺序表示。负载数据的长度为扩展数据及应用数据之和,扩展数据的长度可能为0,因而此时负载数据的长度就为应用数据的长度。
    185             switch (len)
    186             {
    187                 case 126:
    188                     df.Payloadlen = (UInt16)(data[2] << 8 | data[3]);
    189                     if(df.MASK)
    190                     {
    191                         Buffer.BlockCopy(data, 4, df.MaskingKey, 0, 4);
    192                         df.DataOffset = 8;
    193                     }else
    194                         df.DataOffset = 4;
    195                     break;
    196                 case 127:
    197                     Byte[] byteLen = new Byte[8];
    198                     Buffer.BlockCopy(data, 4, byteLen, 0, 8);
    199                     df.Payloadlen = (int)BitConverter.ToUInt64(byteLen, 0);
    200                     if (df.MASK)
    201                     {
    202                         Buffer.BlockCopy(data, 10, df.MaskingKey, 0, 4);
    203                         df.DataOffset = 14;
    204                     }
    205                     else
    206                         df.DataOffset = 10;
    207                     break;
    208                 default:
    209                     if (len < 126)
    210                     {
    211                         df.Payloadlen = len;
    212                         if (df.MASK)
    213                         {
    214                             Buffer.BlockCopy(data, 2, df.MaskingKey, 0, 4);
    215                             df.DataOffset = 6;
    216                         }
    217                         else
    218                             df.DataOffset = 2;
    219                     }
    220                     else
    221                     {
    222                         df.Payloadlen = 0;
    223                         df.DataOffset = 0;
    224                     }
    225                     break;
    226             }
    227             return df;
    228         }
    229         #endregion
    230         #region 处理数据--接收
    231         /*
    232          * PickDataV  方法是出于性能的考虑,用于有时数据只是为了接收,做一些逻辑判断,并不需要对数据块进行单独提炼
    233          * PickData   有两个重载就不赘述了...
    234         */
    235         /// <summary>
    236         /// 如果数据存在掩码就反掩码,具体的使用数据就
    237         /// </summary>
    238         public static void PickDataV(byte[] data, DataFrame dtype)
    239         {
    240             if (dtype.MASK)
    241             {
    242                 int j = 0;
    243                 for (int i = dtype.DataOffset; i < dtype.DataOffset + dtype.Payloadlen; i++)
    244                 {
    245                     data[i] ^= dtype.MaskingKey[j++];
    246                     if (j == 4)
    247                         j = 0;
    248                 }
    249             }
    250         }
    251         public static byte[] PickData(byte[] data, DataFrame dtype)
    252         {
    253             byte[] byt = new byte[dtype.Payloadlen];
    254             PickDataV(data, dtype);
    255             Buffer.BlockCopy(data, dtype.DataOffset, byt, 0, dtpye.Payloadlen);
    256             return byt;
    257         }
    258         public static string PickData(byte[] data,DataFrame dtype,Encoding encode)
    259         {
    260             PickDataV(data, dtype);
    261             return encode.GetString(data, dtype.DataOffset, dtype.Payloadlen);
    262         }
    263         #endregion
    264         #region 处理数据--发送
    265         /*
    266          * PackData         两个重载,用于组装无掩码数据
    267          * PackMaskData     两个重载,用于将数据掩码后组装
    268         */
    269         /// <summary>
    270         /// 组装无掩码数据,一般用于服务端向客户端发送
    271         /// </summary>
    272         public static byte[] PackData(string data, Encoding encode, OpcodeType dwOpcode = OpcodeType.Text)
    273         {
    274             //字符默认用UTF8编码处理,如果有别的编码需求,自行处理后用下面的重载函数
    275             byte[] buff = encode.GetBytes(data);
    276             return PackData(buff, dwOpcode);
    277         }
    278         public static byte[] PackData(byte[] buff, OpcodeType dwOpcode = OpcodeType.Text)
    279         {
    280             List<byte> byt = new List<byte>();
    281             byt.Add((byte)(0x80 | (byte)dwOpcode));
    282             if (buff.Length < 126)
    283                 byt.Add((byte)buff.Length);
    284             else if (buff.Length <= ushort.MaxValue)
    285             {
    286                 ushort l = (ushort)buff.Length;
    287                 byte[] bl = BitConverter.GetBytes(l);
    288                 byt.Add(0x7e);
    289                 byt.Add(bl[1]);
    290                 byt.Add(bl[0]);
    291             }
    292             else
    293             {
    294                 //由于用不到,未做测试
    295                 ulong l = (ulong)buff.Length;
    296                 byt.Add(0x7f);
    297                 byt.AddRange(BitConverter.GetBytes(l));
    298             }
    299             byt.AddRange(buff);
    300             return byt.ToArray();
    301         }
    302         
    303         /// <summary>
    304         /// 将数据掩码后组装,一般是客户端向服务端发送
    305         /// </summary>
    306         public static byte[] PackMaskData(string str, Encoding encode, OpcodeType dwOpcode = OpcodeType.Text)
    307         {
    308             byte[] byt = encode.GetBytes(str);
    309             return PackMaskData(byt, dwOpcode);
    310         }
    311         public static byte[] PackMaskData(byte[] byt, OpcodeType dwOpcode = OpcodeType.Text)
    312         {
    313             List<byte> data = new List<byte>();
    314             //掩码我用的是固定值,有需要也可以自己做成随机的
    315             byte[] maskey ={ 13, 113, 213, 177 };
    316             int j = 0;
    317             for (int i = 0; i < byt.Length; i++)
    318             {
    319                 data[i] ^= maskey[j++];
    320                 if (j > 3) j = 0;
    321             }
    322             data.Add((byte)(0x80 | (byte)OpcodeType.Text));//第一字节,FIN+RSV1+RSV2+RSV3+opcode
    323             if (byt.Length < 126)
    324             {
    325                 data.Add((Byte)(0x80 | (Byte)byt.Length));
    326             }
    327             else if (byt.Length <= ushort.MaxValue)//65535
    328             {
    329                 data.Add(254);//固定 掩码位+126
    330                 byte[] b=BitConverter.GetBytes((ushort)byt.Length);
    331                 data.Add(b[1]);//反转
    332                 data.Add(b[0]);
    333             }
    334             else
    335             {
    336                 //这部分没有经过实际测试,依靠协议文档推写出来的
    337                 //我的需求只是聊天通信,若有传送文件等需求,请自行测试
    338                 data.Add(255);//固定 掩码位+127
    339                 byte[] b = BitConverter.GetBytes((ulong)byt.Length);
    340                 Array.Reverse(b);//反转
    341                 data.AddRange(b);
    342             }
    343             data.AddRange(byt);
    344             data.AddRange(maskey);
    345             return data.ToArray();
    346         }
    347         #endregion
    348         #region 常用控制帧
    349         /*
    350             下面的 Ping、Pong、Close 是非掩码信号,用于服务端向客户端发送,如果客户端想服务端发送就需要掩码
    351             使用的时候直接  socket.Send(WSProtocol.PingFrame, 0, WSProtocol.PingFrame.Length);
    352             我用的0长度,其实是可以包含数据的,但是附带数据客户端处理又麻烦了
    353 
    354             * 如果有附带信息的需求,也可以用上面[发送]里的函数,可选参数指定OpcodeType
    355             * 特别注意:收到ping的时候,回应pong.而收到pong的时候,回应还是pong
    356             * 在协议里,ping是最为要求应答信号的,而pong是作为单向心跳检测的
    357          */
    358         private static byte[] dwPing = { 0x89, 0x0 };
    359         private static byte[] dwPong = { 0x8a, 0x0 };
    360         private static byte[] dwClose = { 0x88, 0x0 };
    361         public static byte[] PingFrame { get { return dwPing; } }
    362         public static byte[] PongFrame { get { return dwPong; } }
    363         public static byte[] CloseFrame { get { return dwClose; } }
    364         #endregion
    365     }
    366 }
  • 相关阅读:
    css 元素垂直居中
    win7定时关机
    tabel使用总结
    Js获取当前日期时间及其它操作
    织梦dedecms自定义表单设置必填项
    css字体文本格式 鼠标样式
    css溢出文本显示省略号
    java注解学习
    自定义JSON返回字段
    Spring-解决请求中文乱码问题
  • 原文地址:https://www.cnblogs.com/xiii/p/7277663.html
Copyright © 2011-2022 走看看