LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

C#实现服务端WebSocket

admin
2019年11月12日 17:8 本文热度 3525

最近测试了一下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-2024 ClickSun All Rights Reserved