- Kaazing WebSocket Gateway(一個 Java 實現的 WebSocket Server);
- mod_pywebsocket(一個 Python 實現的 WebSocket Server);
- Netty(一個 Java 實現的網絡框架其中包括了對 WebSocket 的支持);
- node.js(一個 Server 端的 JavaScript 框架提供了對 WebSocket 的支持);
- WebSocket4Net(一個.net的服務器端實現);
其實在目前的.net4.5框架中已經實現了WebSocket,不用官方實現,我們自己來寫個簡單的。服務器端需要根據協議來握手、接收和發送。
握手
首先我們再來回顧下握手協議:
- 客戶端發到服務器的內容:
- GET /chat HTTP/1.1
- Host: server.example.com
- Upgrade: websocket
- Connection: Upgrade
- Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
- Origin: http://example.com
- Sec-WebSocket-Protocol: chat, superchat
- Sec-WebSocket-Version: 13
- 從服務器到客戶端的內容:
- HTTP/1.1 101 Switching Protocols
- Upgrade: websocket
- Connection: Upgrade
- Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
- Sec-WebSocket-Protocol: chat
- 取出Sec-WebSocket-Key,與一個magic string “258EAFA5-E914-47DA-95CA-C5AB0DC85B11” 連接成一個新的key串;
- 將新的key串SHA1編碼,生成一個由多組兩位16進制數構成的加密串;
- 把加密串進行base64編碼生成最終的key,這個key就是Sec-WebSocket-Key;
- /// <summary>
- /// 生成Sec-WebSocket-Accept
- /// </summary>
- /// <param name="handShakeText">客戶端握手信息</param>
- /// <returns>Sec-WebSocket-Accept</returns>
- private static string GetSecKeyAccetp(byte[] handShakeBytes,int bytesLength)
- {
- string handShakeText = Encoding.UTF8.GetString(handShakeBytes, 0, bytesLength);
- string key = string.Empty;
- Regex r = new Regex(@"Sec\-WebSocket\-Key:(.*?)\r\n");
- Match m = r.Match(handShakeText);
- if (m.Groups.Count != 0)
- {
- key = Regex.Replace(m.Value, @"Sec\-WebSocket\-Key:(.*?)\r\n", "$1").Trim();
- }
- byte[] encryptionString = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
- return Convert.ToBase64String(encryptionString);
- }
解析接收的客戶端信息
接收到客戶端數據解析規則如下:
- 1byte
1bit: frame-fin,x0表示該message後續還有frame;x1表示是message的最後一個frame
3bit: 分別是frame-rsv1、frame-rsv2和frame-rsv3,通常都是x0
4bit: frame-opcode,x0表示是延續frame;x1表示文本frame;x2表示二進制frame;x3-7保留給非控制frame;x8表示關 閉連接;x9表示ping;xA表示pong;xB-F保留給控制frame - 2byte
1bit: Mask,1表示該frame包含掩碼;0,表示無掩碼
7bit、7bit+2byte、7bit+8byte: 7bit取整數值,若在0-125之間,則是負載數據長度;若是126表示,後兩個byte取無符號16位整數值,是負載長度;127表示後8個 byte,取64位無符號整數值,是負載長度 - 3-6byte: 這裏假定負載長度在0-125之間,並且Mask爲1,則這4個byte是掩碼
- 7-end byte: 長度是上面取出的負載長度,包括擴展數據和應用數據兩部分,通常沒有擴展數據;若Mask爲1,則此數據需要解碼,解碼規則爲1-4byte掩碼循環和數據byte做異或操作。
- /// <summary>
- /// 解析客戶端數據包
- /// </summary>
- /// <param name="recBytes">服務器接收的數據包</param>
- /// <param name="recByteLength">有效數據長度</param>
- /// <returns></returns>
- private static string AnalyticData(byte[] recBytes, int recByteLength)
- {
- if (recByteLength < 2) { return string.Empty; }
- bool fin = (recBytes[0] & 0x80) == 0x80; // 1bit,1表示最後一幀
- if (!fin){
- return string.Empty;// 超過一幀暫不處理
- }
- bool mask_flag = (recBytes[1] & 0x80) == 0x80; // 是否包含掩碼
- if (!mask_flag){
- return string.Empty;// 不包含掩碼的暫不處理
- }
- int payload_len = recBytes[1] & 0x7F; // 數據長度
- byte[] masks = new byte[4];
- byte[] payload_data;
- if (payload_len == 126){
- Array.Copy(recBytes, 4, masks, 0, 4);
- payload_len = (UInt16)(recBytes[2] << 8 | recBytes[3]);
- payload_data = new byte[payload_len];
- Array.Copy(recBytes, 8, payload_data, 0, payload_len);
- }else if (payload_len == 127){
- Array.Copy(recBytes, 10, masks, 0, 4);
- byte[] uInt64Bytes = new byte[8];
- for (int i = 0; i < 8; i++){
- uInt64Bytes[i] = recBytes[9 - i];
- }
- UInt64 len = BitConverter.ToUInt64(uInt64Bytes, 0);
- payload_data = new byte[len];
- for (UInt64 i = 0; i < len; i++){
- payload_data[i] = recBytes[i + 14];
- }
- }else{
- Array.Copy(recBytes, 2, masks, 0, 4);
- payload_data = new byte[payload_len];
- Array.Copy(recBytes, 6, payload_data, 0, payload_len);
- }
- for (var i = 0; i < payload_len; i++){
- payload_data[i] = (byte)(payload_data[i] ^ masks[i % 4]);
- }
- return Encoding.UTF8.GetString(payload_data);
- }
發送數據至客戶端
服務器發送的數據以0x81開頭,緊接發送內容的長度(若長度在0-125,則1個byte表示長度;若長度不超過0xFFFF,則後2個byte 作爲無符號16位整數表示長度;若超過0xFFFF,則後8個byte作爲無符號64位整數表示長度),最後是內容的byte數組。
代碼如下:
- /// <summary>
- /// 打包服務器數據
- /// </summary>
- /// <param name="message">數據</param>
- /// <returns>數據包</returns>
- private static byte[] PackData(string message)
- {
- byte[] contentBytes = null;
- byte[] temp = Encoding.UTF8.GetBytes(message);
- if (temp.Length < 126){
- contentBytes = new byte[temp.Length + 2];
- contentBytes[0] = 0x81;
- contentBytes[1] = (byte)temp.Length;
- Array.Copy(temp, 0, contentBytes, 2, temp.Length);
- }else if (temp.Length < 0xFFFF){
- contentBytes = new byte[temp.Length + 4];
- contentBytes[0] = 0x81;
- contentBytes[1] = 126;
- contentBytes[2] = (byte)(temp.Length & 0xFF);
- contentBytes[3] = (byte)(temp.Length >> 8 & 0xFF);
- Array.Copy(temp, 0, contentBytes, 4, temp.Length);
- }else{
- // 暫不處理超長內容
- }
- return contentBytes;
- }