LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

C#實現服務端WebSocket

admin
2019年11月12日 17:8 本文熱度 3825

最近測試了一下WebSocket , 服務端WebSocket使用C#的原生ssocket實現。前端使用js的WebSocket

值得提醒的是本次測試的重點:

①:前端和后端額外的握手

②:后端取得前端數據后如何解析

③:后端往前端發數據的打包流程



后端Socket核心

using System;
using System.ComponentModel;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Linq;
using SWebSocketLib.com;
namespace SWebSocketLib
{
    /// <summary>
    /// WebSocket
    /// </summary>
    public sealed class WebSocket_Service
    {
        
        byte[] buffer = new byte[1024];
        private Socket _listener = null;//服務端Socket
        private readonly IPEndPoint _ip_port = null;
        private readonly int _listen_count = 0;
        private Action<Socket_CallBack_Type, Object, Socket> _callback = null;
        public WebSocket_Service( IPEndPoint ip_port , Action<Socket_CallBack_Type , Object , Socket> callback ,int listen_count = 100 )
        {
            this._ip_port = ip_port;
            this._callback = callback;
            this._listen_count = listen_count;
        }
        /// <summary>
        /// 啟動監聽
        /// </summary>
        public void Start()
        {
            if (this._listener == null)
                this._listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this._listener.Bind(this._ip_port);
            this._listener.Listen(this._listen_count);
            this.accept();
        }
        /// <summary>
        /// 等待連接
        /// </summary>
        private void accept()
        {
            Console.WriteLine("等待客戶端連接....");
            Socket sc = _listener.Accept();//接受一個連接
            Console.WriteLine("接受到了客戶端:" + sc.RemoteEndPoint.ToString() + "連接....");
            this.Receive2HandShake(sc);
        }
        #region 正式接收客戶端信息
        private void Receive2Msg(Socket client)
        {
            Console.WriteLine("等待客戶端數據....");
            int length = client.Receive(buffer);//接受客戶端信息
            if (length > 0)
            {
                string clientMsg = AnalyticData(buffer, length);
                Console.WriteLine("接受到客戶端數據:" + clientMsg);
                if (clientMsg == @"Hello , I am Egret Test By Aonaufly")
                {
                    this._callback(Socket_CallBack_Type.connect_succ, null, client);//發送連接成功信息
                }
                //this.Receive2Msg(client);
            }
            else 
            {
                client.Disconnect(true);
            }
        }
        #endregion
        #region 向服務端發送信息
        public void Send2Msg(Socket client, string clientMsg)
        {
            Console.WriteLine("==發送數據:" + clientMsg + " 至客戶端....");
            byte[] body = Encoding.UTF8.GetBytes(clientMsg);
            byte[] c = this.PackData(body);
            Console.WriteLine("數據的大小 : {0} / 包的大小 : {1}", body.Length , c.Length);
            client.Send(c);
        }
        #endregion
        #region 接收客戶端發來的握手信息并返回握手信息
        /// <summary>
        /// 第四次握手信息處理
        /// </summary>
        /// <param name="client"></param>
        private void Receive2HandShake(Socket client)
        {
            int length = client.Receive(buffer);
            if (length > 0)
            {
                client.Send(PackHandShakeData(GetSecKeyAccetp(buffer, length)));
                Console.WriteLine("已經發送握手協議了....");
                this.Receive2Msg(client);
            }
            else
            {
                client.Disconnect(true);
            }
        }
        #endregion
        #region WebSocket的的四次額外握手信息
        /// <summary>
        /// 打包握手信息
        /// </summary>
        /// <param name="secKeyAccept"></param>
        /// <returns></returns>
        private static byte[] PackHandShakeData(string secKeyAccept)
        {
            var responseBuilder = new StringBuilder();
            responseBuilder.Append("HTTP/1.1 101 Switching Protocols" + Environment.NewLine);
            responseBuilder.Append("Upgrade: websocket" + Environment.NewLine);
            responseBuilder.Append("Connection: Upgrade" + Environment.NewLine);
            responseBuilder.Append("Sec-WebSocket-Accept: " + secKeyAccept + Environment.NewLine + Environment.NewLine);
            return Encoding.UTF8.GetBytes(responseBuilder.ToString());
        }
        /// <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);
        }
        #endregion
        #region 解析客戶端數據
        /// <summary>
        /// 解析客戶端數據包
        /// </summary>
        /// <param name="recBytes">服務器接收的數據包</param>
        /// <param name="recByteLength">有效數據長度</param>
        /// <returns></returns>
        private 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);
        }
        #endregion
        #region 打包要發送的數據
        /// <summary>
        /// 打包服務器數據
        /// </summary>
        /// <param name="message">需要打包的數據(通訊用)</param>
        /// <returns>數據包</returns>
        private byte[] PackData(byte[] message)
        {
            byte[] contentBytes = null;
            if (message.Length < 126)
            {
                contentBytes = new byte[message.Length + 2];
                contentBytes[0] = 0x81;
                contentBytes[1] = (byte)message.Length;
                Array.Copy(message, 0, contentBytes, 2, message.Length);
            }
            else if (message.Length < 0xFFFF)
            {
                contentBytes = new byte[message.Length + 4];
                contentBytes[0] = 0x81;
                contentBytes[1] = 126;
                
                
                contentBytes[2] = (byte)(message.Length & 0xFF);
                contentBytes[3] = (byte)(message.Length >> 8 & 0xFF);
                Array.Copy(message, 0, contentBytes, 4, message.Length);
            }
            else
            {
                contentBytes = new byte[message.Length + 10];  
                contentBytes[0] = 0x81;
                contentBytes[1] = 127;
                byte[] ulonglen = BitConverter.GetBytes((long)message.Length);
                contentBytes[2] = ulonglen[7];
                contentBytes[3] = ulonglen[6];
                contentBytes[4] = ulonglen[5];
                contentBytes[5] = ulonglen[4];
                contentBytes[6] = ulonglen[3];
                contentBytes[7] = ulonglen[2];
                contentBytes[8] = ulonglen[1];
                contentBytes[9] = ulonglen[0];
                Array.Copy(message, 0, contentBytes, 10, message.Length);  
            }
            return contentBytes;
        }  
        #endregion 

    }
    /// <summary>
    /// Socket回調類型
    /// </summary>
    public enum Socket_CallBack_Type : uint
    {
        [Description("連接成功")]
        connect_succ = 0,
        [Description("連接失敗")]
        connect_fail = 1,
        [Description("接收返回")]
        receive = 2,
        [Description("發送返回")]
        send = 3,
        [Description("客戶端被迫斷開")]
        client_passive_off = 4,
        [Description("客戶端主動斷開")]
        client_active_off = 5
    }
}

講解 :

①:關于握手(websocket存在第四次額外的握手),客戶端請求連接服務器后,服務器會根據客戶端發來的信息,生成一個握手信息發給客戶端完成最后一次額外的握手。

以下是服務端生成握手信息的核心代碼 :

        /// <summary>
        /// 打包握手信息
        /// </summary>
        /// <param name="secKeyAccept"></param>
        /// <returns></returns>
        private static byte[] PackHandShakeData(string secKeyAccept)
        {
            var responseBuilder = new StringBuilder();
            responseBuilder.Append("HTTP/1.1 101 Switching Protocols" + Environment.NewLine);
            responseBuilder.Append("Upgrade: websocket" + Environment.NewLine);
            responseBuilder.Append("Connection: Upgrade" + Environment.NewLine);
            responseBuilder.Append("Sec-WebSocket-Accept: " + secKeyAccept + Environment.NewLine + Environment.NewLine);
            return Encoding.UTF8.GetBytes(responseBuilder.ToString());
        }
        /// <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);
        }

②:關于解析客戶端發來的信息(目前只解析成String , 可以在此方法上擴展的)

        /// <summary>
        /// 解析客戶端數據包
        /// </summary>
        /// <param name="recBytes">服務器接收的數據包</param>
        /// <param name="recByteLength">有效數據長度</param>
        /// <returns></returns>
        private 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);
        }

③:發送到客戶端的信息也是需要打包的

        /// <summary>
        /// 打包服務器數據
        /// </summary>
        /// <param name="message">需要打包的數據(通訊用)</param>
        /// <returns>數據包</returns>
        private byte[] PackData(byte[] message)
        {
            byte[] contentBytes = null;
            if (message.Length < 126)
            {
                contentBytes = new byte[message.Length + 2];
                contentBytes[0] = 0x81;
                contentBytes[1] = (byte)message.Length;
                Array.Copy(message, 0, contentBytes, 2, message.Length);
            }
            else if (message.Length < 0xFFFF)
            {
                contentBytes = new byte[message.Length + 4];
                contentBytes[0] = 0x81;
                contentBytes[1] = 126;
                
                
                contentBytes[2] = (byte)(message.Length & 0xFF);
                contentBytes[3] = (byte)(message.Length >> 8 & 0xFF);
                Array.Copy(message, 0, contentBytes, 4, message.Length);
            }
            else
            {
                contentBytes = new byte[message.Length + 10];  
                contentBytes[0] = 0x81;
                contentBytes[1] = 127;
                byte[] ulonglen = BitConverter.GetBytes((long)message.Length);
                contentBytes[2] = ulonglen[7];
                contentBytes[3] = ulonglen[6];
                contentBytes[4] = ulonglen[5];
                contentBytes[5] = ulonglen[4];
                contentBytes[6] = ulonglen[3];
                contentBytes[7] = ulonglen[2];
                contentBytes[8] = ulonglen[1];
                contentBytes[9] = ulonglen[0];
                Array.Copy(message, 0, contentBytes, 10, message.Length);  
            }
            return contentBytes;
        }

客戶端 (index.html)

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <meta charset="utf-8"/>
  5.     <title>Testing websockets</title>
  6. </head>
  7. <body>
  8. <button id="start">click to start</button>
  9. <div id="messages"></div>
  10. <script type="text/javascript">
  11.     var button = document.getElementById("start");
  12.     button.onclick = function ({
  13.         var websocketAddr = "ws://192.168.1.104:6065/";
  14.         var webSocket = new WebSocket(websocketAddr);
  15.         webSocket.onopen = function (event{
  16.             document.getElementById('messages').innerHTML
  17.                     = '消息通道開啟,可以發送消息了';
  18.             webSocket.send('Hello , I am Egret Test By Aonaufly');
  19.         };
  20.         webSocket.onmessage = function (event{
  21.             document.getElementById('messages').innerHTML
  22.                     += '<br />來自服務器的消息' + event.data;
  23.         };
  24.         webSocket.onerror = function (event{
  25.             alert(event.data);
  26.         };
  27.     }
  28. </script>
  29. </body>
  30. </html>

測試結果:

服務端



客戶端


實現了一次完美的通訊。


該文章在 2019/11/12 17:14:48 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業的專業生產管理軟件系統,系統成熟度和易用性得到了國內大量中小企業的青睞。
點晴PMS碼頭管理系統主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業務管理,結合碼頭的業務特點,圍繞調度、堆場作業而開發的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業的高效ERP管理信息系統。
點晴WMS倉儲管理系統提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統,標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統。
Copyright 2010-2025 ClickSun All Rights Reserved

黄频国产免费高清视频,久久不卡精品中文字幕一区,激情五月天AV电影在线观看,欧美国产韩国日本一区二区
亚洲色婷成人综合电影在线 | 性深夜福利免费网站 | 青青青青青久久精品国产首页 | 亚洲中文字幕乱码影视 | 日本一道久久高清国产 | 呦亚洲欧美在线观看 |