C#封裝的websocket協議類

關於VB版以前已經寫了,有須要能夠進傳送門《VB封裝的WebSocket模塊,拿來即用》兩個使用都差很少,這裏簡單概述一下:html

鏈接完成後,沒有握手就用Handshake()先完成握手
以後接收數據,先用AnalyzeHeader()獲得數據幀結構(DataFrame)
而後再用PickDataV()PickData()獲得源數據,對於掩碼數據是在這裏反掩碼
關於發送數據,則是:
服務端發送無需掩碼用PackData()將數據組裝一下就能夠發送
而模擬客戶端向服務器的發送須要加掩碼,用PackMaskData()前端

相關資料下載:《WebSocket協議中文版.pdf》
*等有時間我再作個demo供下載吧,這個類使用還算簡單web

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\r\n");
130             response.Append("Upgrade: WebSocket\r\n");
131             response.Append("Connection: Upgrade\r\n");
132             response.AppendFormat("Sec-WebSocket-Accept: {0}\r\n", acceptKey);
133             response.AppendFormat("WebSocket-Origin: {0}\r\n", getCilentWSKey(request, "Sec-WebSocket-Origin"));
134             response.AppendFormat("WebSocket-Location: {0}\r\n", getCilentWSKey(request, "Host"));
135             response.Append("\r\n");
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("\r\n", 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 }
相關文章
相關標籤/搜索