關於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 }