亲宝软件园·资讯

展开

C#聊天室功能

打锤子棒棒糖 人气:0

WebSocket介绍

WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。

当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。

其实WebSocket与Socket区别不大,只是客户端是在浏览器上实现的,替代了传统的轮询机制,减少带宽和资源

C#中WebSocket定义事件

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace WebSocketsServer
{
    /// <summary>
    /// 声明新连接处理事件
    /// </summary>
    /// <param name="loginName"></param>
    /// <param name="e"></param>
    public delegate void NewConnection_EventHandler(string loginName, EventArgs args);
 
    /// <summary>
    /// 声明接收数据处理事件
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="message"></param>
    /// <param name="args"></param>
    public delegate void DataReceive_EventHandler(object sender, string message, EventArgs args);
 
    /// <summary>
    /// 声明断开连接处理事件
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    public delegate void Disconncetion_EventHandler(object sender, string message, EventArgs args);
}

WebSocket服务端实现代码

WebSocketServer代码

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
 
namespace WebSocketsServer
{
    /// <summary>
    /// Socket服务端
    /// </summary>
    public class WebSocketServer : IDisposable
    {
        #region 私有变量
        /// <summary>
        /// ip
        /// </summary>
        private string _ip = string.Empty;
        /// <summary>
        /// 端口
        /// </summary>
        private int _port = 0;
        /// <summary>
        /// 服务器地址
        /// </summary>
        private string _serverLocation = string.Empty;
        /// <summary>
        /// Socket对象
        /// </summary>
        private Socket _socket = null;
        /// <summary>
        /// 监听的最大连接数
        /// </summary>
        private int maxListenConnect = 10;
        /// <summary>
        /// 是否关闭Socket对象
        /// </summary>
        private bool isDisposed = false;
 
        private Logger logger = null;
        /// <summary>
        /// buffer缓存区字节数
        /// </summary>
        private int maxBufferSize = 0;
        /// <summary>
        /// 第一个字节,以0x00开始
        /// </summary>
        private byte[] FirstByte;
        /// <summary>
        /// 最后一个字节,以0xFF结束
        /// </summary>
        private byte[] LastByte;
        #endregion
 
        #region 声明Socket处理事件
        /// <summary>
        /// Socket新连接事件
        /// </summary>
        public event NewConnection_EventHandler NewConnectionHandler;
        /// <summary>
        /// Socket接收消息事件
        /// </summary>
        public event DataReceive_EventHandler DataReceiveHandler;
        /// <summary>
        /// Socket断开连接事件
        /// </summary>
        public event Disconncetion_EventHandler DisconnectionHandler;
        #endregion
 
        /// <summary>
        /// 存放SocketConnection集合
        /// </summary>
        List<SocketConnection> SocketConnections = new List<SocketConnection>();
 
        #region 构造函数
        public WebSocketServer()
        {
            this._ip = GetLocalMachineIPAddress().ToString();
            this._port = 9000;
            this._serverLocation = string.Format("ws://{0}:{1}", this._ip, this._port);
            Initialize();
        }
        public WebSocketServer(string ip, int port)
        {
            this._ip = ip;
            this._port = port;
            this._serverLocation = string.Format("ws://{0}:{1}", this._ip, this._port);
            Initialize();
        }
        public WebSocketServer(string ip, int port, string serverLocation)
        {
            this._ip = ip;
            this._port = port;
            this._serverLocation = serverLocation;
            Initialize();
        }
        #endregion
 
        /// <summary>
        /// 初始化私有变量
        /// </summary>
        private void Initialize()
        {
            isDisposed = false;
            logger = new Logger()
            {
                LogEvents = true
            };
            maxBufferSize = 1024 * 1024;
            maxListenConnect = 500;
            FirstByte = new byte[maxBufferSize];
            LastByte = new byte[maxBufferSize];
            FirstByte[0] = 0x00;
            LastByte[0] = 0xFF;
        }
 
        /// <summary>
        /// 开启服务
        /// </summary>
        public void StartServer()
        {
            try
            {
                //实例化套接字
                _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //创建IP对象
                IPAddress address = GetLocalMachineIPAddress();
                //创建网络端点,包括ip和port
                IPEndPoint endPoint = new IPEndPoint(address, _port);
                //将socket与本地端点绑定
                _socket.Bind(endPoint);
                //设置最大监听数
                _socket.Listen(maxListenConnect);
 
                logger.Log(string.Format("聊天服务器启动。监听地址:{0}, 端口:{1}", this._ip, this._port));
                logger.Log(string.Format("WebSocket服务器地址: ws://{0}:{1}", this._ip, this._port));
 
                //开始监听客户端
                Thread thread = new Thread(ListenClientConnect);
                thread.Start();
            }
            catch (Exception ex)
            {
                logger.Log(ex.Message);
            }
        }
 
        /// <summary>
        /// 监听客户端连接
        /// </summary>
        private void ListenClientConnect()
        {
            try
            {
                while (true)
                {
                    //为新建连接创建的Socket
                    Socket socket = _socket.Accept();
                    if (socket != null)
                    {
                        //线程不休眠的话,会导致回调函数的AsyncState状态出异常
                        Thread.Sleep(100);
                        SocketConnection socketConnection = new SocketConnection(this._ip, this._port, this._serverLocation)
                        {
                            ConnectionSocket = socket
                        };
                        //绑定事件
                        socketConnection.NewConnectionHandler += SocketConnection_NewConnectionHandler;
                        socketConnection.DataReceiveHandler += SocketConnection_DataReceiveHandler;
                        socketConnection.DisconnectionHandler += SocketConnection_DisconnectionHandler;
                        //从开始连接的Socket中异步接收消息
                        socketConnection.ConnectionSocket.BeginReceive(socketConnection.receivedDataBuffer,
                                        0, socketConnection.receivedDataBuffer.Length,
                                        0, new AsyncCallback(socketConnection.ManageHandshake),
                                        socketConnection.ConnectionSocket.Available);
                        //存入集合,以便在Socket发送消息时发送给所有连接的Socket套接字
                        SocketConnections.Add(socketConnection);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
 
        }
 
        /// <summary>
        /// SocketConnection监听的新连接事件
        /// </summary>
        /// <param name="loginName"></param>
        /// <param name="args"></param>
        private void SocketConnection_NewConnectionHandler(string loginName, EventArgs args)
        {
            NewConnectionHandler?.Invoke(loginName, EventArgs.Empty);
        }
        /// <summary>
        /// SocketConnection监听的消息接收事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="msgData"></param>
        /// <param name="args"></param>
        private void SocketConnection_DataReceiveHandler(object sender, string msgData, EventArgs args)
        {
            //新用户连接进来时显示欢迎信息
            //SocketConnection socketConnection = sender as SocketConnection;
            Send(msgData);
        }
        /// <summary>
        /// SocketConnection监听的断开连接事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void SocketConnection_DisconnectionHandler(object sender, string message, EventArgs args)
        {
            if (sender is SocketConnection socket)
            {
                Send(message);
                socket.ConnectionSocket.Close();
                SocketConnections.Remove(socket);
            }
        }
 
        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="message"></param>
        public void Send(string message)
        {
            //给所有连接上的发送消息
            foreach (SocketConnection socket in SocketConnections)
            {
                if (!socket.ConnectionSocket.Connected)
                {
                    continue;
                }
                try
                {
                    if (socket.IsDataMasked)
                    {
                        DataFrame dataFrame = new DataFrame(message);
                        socket.ConnectionSocket.Send(dataFrame.GetBytes());
                    }
                    else
                    {
                        socket.ConnectionSocket.Send(FirstByte);
                        socket.ConnectionSocket.Send(Encoding.UTF8.GetBytes(message));
                        socket.ConnectionSocket.Send(LastByte);
                    }
                }
                catch (Exception ex)
                {
                    logger.Log(ex.Message);
                }
            }
        }
 
        /// <summary>
        /// 获取当前主机的IP地址
        /// </summary>
        /// <returns></returns>
        private IPAddress GetLocalMachineIPAddress()
        {
            //获取计算机主机名
            string hostName = Dns.GetHostName();
            //将主机名解析为IPHostEntry
            IPHostEntry hostEntry = Dns.GetHostEntry(hostName);
            foreach (IPAddress address in hostEntry.AddressList)
            {
                //IP4寻址协议
                if (address.AddressFamily == AddressFamily.InterNetwork)
                {
                    return address;
                }
            }
            return hostEntry.AddressList[0];
        }
 
        ~WebSocketServer()
        {
            Close();
        }
 
        public void Dispose()
        {
            Close();
        }
        public void Close()
        {
            if (!isDisposed)
            {
                isDisposed = true;
                if (_socket != null)
                {
                    _socket.Close();
                }
                foreach (SocketConnection socketConnection in SocketConnections)
                {
                    socketConnection.ConnectionSocket.Close();
                }
                SocketConnections.Clear();
                GC.SuppressFinalize(this);
            }
        }
    }
}

自定义的SocketConnection类

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
 
namespace WebSocketsServer
{
    /// <summary>
    /// Socket成功建立的连接
    /// </summary>
    public class SocketConnection
    {
        /// <summary>
        /// 新的Socket连接
        /// </summary>
        public Socket ConnectionSocket = null;
 
        #region Socket监听事件
        /// <summary>
        /// 新连接事件
        /// </summary>
        public event NewConnection_EventHandler NewConnectionHandler;
        /// <summary>
        /// 数据接收事件
        /// </summary>
        public event DataReceive_EventHandler DataReceiveHandler;
        /// <summary>
        /// 断开连接事件
        /// </summary>
        public event Disconncetion_EventHandler DisconnectionHandler;
        #endregion
 
        #region 私有变量
        private string _ip = string.Empty;
        private int _port = 0;
        private string _serverLocation = string.Empty;
 
        private Logger logger;
 
        private string loginId;
        public string LoginId
        {
            get => loginId; set => loginId = value;
        }
        private bool isDataMasked;
        public bool IsDataMasked { get => isDataMasked; set => isDataMasked = value; }
        /// <summary>
        /// 最大缓存区字节数
        /// </summary>
        private int maxBufferSize = 0;
        /// <summary>
        /// 握手协议信息
        /// </summary>
        private string handshake = string.Empty;
        /// <summary>
        /// 握手协议信息(new)
        /// </summary>
        private string newHandshake = string.Empty;
        /// <summary>
        /// 接收消息的数据缓存区
        /// </summary>
        public byte[] receivedDataBuffer;
        private byte[] firstByte;
        private byte[] lastByte;
        private byte[] serverKey1;
        private byte[] serverKey2;
        #endregion
 
        #region 构造函数
        public SocketConnection()
        {
            Initialize();
        }
 
        public SocketConnection(string ip, int port, string serverLocation)
        {
            this._ip = ip;
            this._port = port;
            this._serverLocation = serverLocation;
            Initialize();
        }
        #endregion
 
        /// <summary>
        /// 初始化变量
        /// </summary>
        private void Initialize()
        {
            logger = new Logger();
            maxBufferSize = 1024 * 1024;
            receivedDataBuffer = new byte[maxBufferSize];
            firstByte = new byte[maxBufferSize];
            lastByte = new byte[maxBufferSize];
            firstByte[0] = 0x00;
            lastByte[0] = 0xFF;
 
            //webSocket携带头信息
            handshake = "HTTP/1.1 101 Web Socket Protocol Handshake" + Environment.NewLine;
            handshake += "Upgrade: WebSocket" + Environment.NewLine;
            handshake += "Connection: Upgrade" + Environment.NewLine;
            handshake += "Sec-WebSocket-Origin: " + "{0}" + Environment.NewLine;
            handshake += string.Format("Sec-WebSocket-Location: " + "ws://{0}:{1}" + Environment.NewLine, this._ip, this._port);
            handshake += Environment.NewLine;
 
            newHandshake = "HTTP/1.1 101 Switching Protocols" + Environment.NewLine;
            newHandshake += "Upgrade: WebSocket" + Environment.NewLine;
            newHandshake += "Connection: Upgrade" + Environment.NewLine;
            newHandshake += "Sec-WebSocket-Accept: {0}" + Environment.NewLine;
            newHandshake += Environment.NewLine;
        }
 
        /// <summary>
        /// 处理异步接收消息回调方法
        /// </summary>
        /// <param name="asyncResult"></param>
        public void ManageHandshake(IAsyncResult asyncResult)
        {
            try
            {
                string header = "Sec-WebSocket-Version:";
                int HandshakeLength = (int)asyncResult.AsyncState;
                byte[] last8Bytes = new byte[8];
 
                UTF8Encoding encoding = new UTF8Encoding();
                String rawClientHandshake = encoding.GetString(receivedDataBuffer, 0, HandshakeLength);
 
                Array.Copy(receivedDataBuffer, HandshakeLength - 8, last8Bytes, 0, 8);
                //现在使用的是比较新的WebSocket协议
                if (rawClientHandshake.IndexOf(header) != -1)
                {
                    this.isDataMasked = true;
                    string[] rawClientHandshakeLines = rawClientHandshake.Split(new string[] { Environment.NewLine }, System.StringSplitOptions.RemoveEmptyEntries);
 
                    string acceptKey = "";
                    foreach (string line in rawClientHandshakeLines)
                    {
                        if (line.Contains("Sec-WebSocket-Key:"))
                        {
                            acceptKey = ComputeWebSocketHandshakeSecurityHash09(line.Substring(line.IndexOf(":") + 2));
                        }
                    }
                    newHandshake = string.Format(newHandshake, acceptKey);
                    byte[] newHandshakeText = Encoding.UTF8.GetBytes(newHandshake);
                    //将数据异步发送到连接的socket上
                    ConnectionSocket.BeginSend(newHandshakeText, 0, newHandshakeText.Length, SocketFlags.None, HandshakeFinished, null);
                    return;
                }
 
                string clientHandshake = encoding.GetString(receivedDataBuffer, 0, receivedDataBuffer.Length - 8);
                string[] clientHandshakeLines = clientHandshake.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
 
                logger.Log("新的连接请求来自:" + ConnectionSocket.LocalEndPoint + ".正准备进行连接...");
 
                // Welcome the new client
                foreach (string Line in clientHandshakeLines)
                {
                    logger.Log(Line);
                    if (Line.Contains("Sec-WebSocket-Key1:"))
                        BuildServerPartialKey(1, Line.Substring(Line.IndexOf(":") + 2));
                    if (Line.Contains("Sec-WebSocket-Key2:"))
                        BuildServerPartialKey(2, Line.Substring(Line.IndexOf(":") + 2));
                    if (Line.Contains("Origin:"))
                        try
                        {
                            handshake = string.Format(handshake, Line.Substring(Line.IndexOf(":") + 2));
                        }
                        catch
                        {
                            handshake = string.Format(handshake, "null");
                        }
                }
                //为客户端建立响应
                byte[] handshakeText = Encoding.UTF8.GetBytes(handshake);
                byte[] serverHandshakeResponse = new byte[handshakeText.Length + 16];
                byte[] serverKey = BuildServerFullKey(last8Bytes);
                Array.Copy(handshakeText, serverHandshakeResponse, handshakeText.Length);
                Array.Copy(serverKey, 0, serverHandshakeResponse, handshakeText.Length, 16);
 
                logger.Log("发送握手信息 ...");
                ConnectionSocket.BeginSend(serverHandshakeResponse, 0, handshakeText.Length + 16, 0, HandshakeFinished, null);
                logger.Log(handshake);
 
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
 
        /// <summary>
        /// 由服务端像客户端发送消息完成回调
        /// </summary>
        /// <param name="asyncResult"></param>
        private void HandshakeFinished(IAsyncResult asyncResult)
        {
            //结束挂起的异步发送
            ConnectionSocket.EndSend(asyncResult);
            ConnectionSocket.BeginReceive(receivedDataBuffer, 0, receivedDataBuffer.Length,
                0, new AsyncCallback(Read), null);
            NewConnectionHandler?.Invoke("", EventArgs.Empty);
        }
 
        private void Read(IAsyncResult asyncResult)
        {
            if (!ConnectionSocket.Connected)
            {
                return;
            }
            string message = string.Empty;
            DataFrame dataFrame = new DataFrame(receivedDataBuffer);
            try
            {
                if (!this.isDataMasked)
                {
                    //WebSocket协议:消息以0x00和0xFF作为填充字节发送
                    UTF8Encoding encoding = new UTF8Encoding();
                    int startIndex = 0;
                    int endIndex = 0;
 
                    // Search for the start byte
                    while (receivedDataBuffer[startIndex] == firstByte[0])
                    {
                        startIndex++;
                    }
                    // Search for the end byte
                    endIndex = startIndex + 1;
                    while (receivedDataBuffer[endIndex] != lastByte[0] && endIndex != maxBufferSize - 1)
                    {
                        endIndex++;
                    }
                    if (endIndex == maxBufferSize - 1)
                    {
                        endIndex = maxBufferSize;
                    }
                    // Get the message
                    message = encoding.GetString(receivedDataBuffer, startIndex, endIndex - startIndex);
                }//if
                else
                {
                    message = dataFrame.Text;
                }
 
                if ((message.Length == maxBufferSize && message[0] == Convert.ToChar(65533)) ||
                      message.Length == 0)
                {
                    //断开连接
                    logger.Log("message");
                    if (string.IsNullOrEmpty(message))
                    {
                        MessageInfo messageInfo = new MessageInfo()
                        {
                            MsgType = MessageType.None,
                            Message = ""
                        };
                        message = JsonConvert.SerializeObject(messageInfo);
                    }
                    DisconnectionHandler?.Invoke(this, message, EventArgs.Empty);
                }
                else
                {
                    if (DataReceiveHandler != null)
                    {
                        logger.Log("接受到的信息 [\"" + message + "\"]");
                        //消息发送
                        DataReceiveHandler(this, message, EventArgs.Empty);
                    }
                    Array.Clear(receivedDataBuffer, 0, receivedDataBuffer.Length);
                    ConnectionSocket.BeginReceive(receivedDataBuffer, 0, receivedDataBuffer.Length, 0, Read, null);
                }
            }
            catch (Exception ex)
            {
                logger.Log(ex.Message);
                logger.Log("Socket连接将会被终止.");
                MessageInfo messageInfo = new MessageInfo()
                {
                    MsgType = MessageType.Error,
                    Message = ex.Message + Environment.NewLine + "Socket连接将会被终止"
                };
                DisconnectionHandler?.Invoke(this, JsonConvert.SerializeObject(messageInfo), EventArgs.Empty);
            }
        }
 
        private byte[] BuildServerFullKey(byte[] last8Bytes)
        {
            byte[] concatenatedKeys = new byte[16];
            Array.Copy(serverKey1, 0, concatenatedKeys, 0, 4);
            Array.Copy(serverKey2, 0, concatenatedKeys, 4, 4);
            Array.Copy(last8Bytes, 0, concatenatedKeys, 8, 8);
 
            // MD5 Hash
            MD5 MD5Service = MD5.Create();
            return MD5Service.ComputeHash(concatenatedKeys);
        }
 
        private void BuildServerPartialKey(int keyNum, string clientKey)
        {
            string partialServerKey = "";
            byte[] currentKey;
            int spacesNum = 0;
            char[] keyChars = clientKey.ToCharArray();
            foreach (char currentChar in keyChars)
            {
                if (char.IsDigit(currentChar)) partialServerKey += currentChar;
                if (char.IsWhiteSpace(currentChar)) spacesNum++;
            }
            try
            {
                currentKey = BitConverter.GetBytes((int)(Int64.Parse(partialServerKey) / spacesNum));
                if (BitConverter.IsLittleEndian)
                {
                    Array.Reverse(currentKey);
                }
 
                if (keyNum == 1)
                {
                    serverKey1 = currentKey;
                }
                else
                {
                    serverKey2 = currentKey;
                }
            }
            catch
            {
                if (serverKey1 != null)
                {
                    Array.Clear(serverKey1, 0, serverKey1.Length);
                }
                if (serverKey2 != null)
                {
                    Array.Clear(serverKey2, 0, serverKey2.Length);
                }
            }
        }
 
        private string ComputeWebSocketHandshakeSecurityHash09(string secWebSocketKey)
        {
            const String MagicKEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
            String secWebSocketAccept = String.Empty;
            // 1. Combine the request Sec-WebSocket-Key with magic key.
            String ret = secWebSocketKey + MagicKEY;
            // 2. Compute the SHA1 hash
            SHA1 sha = new SHA1CryptoServiceProvider();
            byte[] sha1Hash = sha.ComputeHash(Encoding.UTF8.GetBytes(ret));
            // 3. Base64 encode the hash
            secWebSocketAccept = Convert.ToBase64String(sha1Hash);
            return secWebSocketAccept;
        }
    }
}

数据文件相关的类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace WebSocketsServer
{
    public class DataFrame
    {
        DataFrameHeader _header;
        private byte[] _extend = new byte[0];
        private byte[] _mask = new byte[0];
        private byte[] _content = new byte[0];
 
        public DataFrame(byte[] buffer)
        {
            //帧头
            _header = new DataFrameHeader(buffer);
 
            //扩展长度
            if (_header.Length == 126)
            {
                _extend = new byte[2];
                Buffer.BlockCopy(buffer, 2, _extend, 0, 2);
            }
            else if (_header.Length == 127)
            {
                _extend = new byte[8];
                Buffer.BlockCopy(buffer, 2, _extend, 0, 8);
            }
 
            //是否有掩码
            if (_header.HasMask)
            {
                _mask = new byte[4];
                Buffer.BlockCopy(buffer, _extend.Length + 2, _mask, 0, 4);
            }
 
            //消息体
            if (_extend.Length == 0)
            {
                _content = new byte[_header.Length];
                Buffer.BlockCopy(buffer, _extend.Length + _mask.Length + 2, _content, 0, _content.Length);
            }
            else if (_extend.Length == 2)
            {
                int contentLength = (int)_extend[0] * 256 + (int)_extend[1];
                _content = new byte[contentLength];
                Buffer.BlockCopy(buffer, _extend.Length + _mask.Length + 2, _content, 0, contentLength > 1024 * 100 ? 1024 * 100 : contentLength);
            }
            else
            {
                long len = 0;
                int n = 1;
                for (int i = 7; i >= 0; i--)
                {
                    len += (int)_extend[i] * n;
                    n *= 256;
                }
                _content = new byte[len];
                Buffer.BlockCopy(buffer, _extend.Length + _mask.Length + 2, _content, 0, _content.Length);
            }
 
            if (_header.HasMask) _content = Mask(_content, _mask);
 
        }
 
        public DataFrame(string content)
        {
            _content = Encoding.UTF8.GetBytes(content);
            int length = _content.Length;
 
            if (length < 126)
            {
                _extend = new byte[0];
                _header = new DataFrameHeader(true, false, false, false, 1, false, length);
            }
            else if (length < 65536)
            {
                _extend = new byte[2];
                _header = new DataFrameHeader(true, false, false, false, 1, false, 126);
                _extend[0] = (byte)(length / 256);
                _extend[1] = (byte)(length % 256);
            }
            else
            {
                _extend = new byte[8];
                _header = new DataFrameHeader(true, false, false, false, 1, false, 127);
 
                int left = length;
                int unit = 256;
 
                for (int i = 7; i > 1; i--)
                {
                    _extend[i] = (byte)(left % unit);
                    left = left / unit;
 
                    if (left == 0)
                        break;
                }
            }
        }
 
        public byte[] GetBytes()
        {
            byte[] buffer = new byte[2 + _extend.Length + _mask.Length + _content.Length];
            Buffer.BlockCopy(_header.GetBytes(), 0, buffer, 0, 2);
            Buffer.BlockCopy(_extend, 0, buffer, 2, _extend.Length);
            Buffer.BlockCopy(_mask, 0, buffer, 2 + _extend.Length, _mask.Length);
            Buffer.BlockCopy(_content, 0, buffer, 2 + _extend.Length + _mask.Length, _content.Length);
            return buffer;
        }
 
        public string Text
        {
            get
            {
                if (_header.OpCode != 1)
                    return string.Empty;
 
                return Encoding.UTF8.GetString(_content);
            }
        }
 
        private byte[] Mask(byte[] data, byte[] mask)
        {
            for (var i = 0; i < data.Length; i++)
            {
                data[i] = (byte)(data[i] ^ mask[i % 4]);
            }
 
            return data;
        }
 
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace WebSocketsServer
{
    public class DataFrameHeader
    {
        private bool _fin;
        private bool _rsv1;
        private bool _rsv2;
        private bool _rsv3;
        private sbyte _opcode;
        private bool _maskcode;
        private sbyte _payloadlength;
 
        public bool FIN { get { return _fin; } }
 
        public bool RSV1 { get { return _rsv1; } }
 
        public bool RSV2 { get { return _rsv2; } }
 
        public bool RSV3 { get { return _rsv3; } }
 
        public sbyte OpCode { get { return _opcode; } }
 
        public bool HasMask { get { return _maskcode; } }
 
        public sbyte Length { get { return _payloadlength; } }
 
        public DataFrameHeader(byte[] buffer)
        {
            if (buffer.Length < 2)
                throw new Exception("无效的数据头.");
 
            //第一个字节
            _fin = (buffer[0] & 0x80) == 0x80;
            _rsv1 = (buffer[0] & 0x40) == 0x40;
            _rsv2 = (buffer[0] & 0x20) == 0x20;
            _rsv3 = (buffer[0] & 0x10) == 0x10;
            _opcode = (sbyte)(buffer[0] & 0x0f);
 
            //第二个字节
            _maskcode = (buffer[1] & 0x80) == 0x80;
            _payloadlength = (sbyte)(buffer[1] & 0x7f);
 
        }
 
        //发送封装数据
        public DataFrameHeader(bool fin, bool rsv1, bool rsv2, bool rsv3, sbyte opcode, bool hasmask, int length)
        {
            _fin = fin;
            _rsv1 = rsv1;
            _rsv2 = rsv2;
            _rsv3 = rsv3;
            _opcode = opcode;
            //第二个字节
            _maskcode = hasmask;
            _payloadlength = (sbyte)length;
        }
 
        //返回帧头字节
        public byte[] GetBytes()
        {
            byte[] buffer = new byte[2] { 0, 0 };
 
            if (_fin) buffer[0] ^= 0x80;
            if (_rsv1) buffer[0] ^= 0x40;
            if (_rsv2) buffer[0] ^= 0x20;
            if (_rsv3) buffer[0] ^= 0x10;
 
            buffer[0] ^= (byte)_opcode;
 
            if (_maskcode) buffer[1] ^= 0x80;
 
            buffer[1] ^= (byte)_payloadlength;
 
            return buffer;
        }
    }
}

自定义的枚举,实体,封装客户端输出类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace WebSocketsServer
{
    public enum MessageType
    {
        Error = -1,
        None = 0,
        /// <summary>
        /// 登录
        /// </summary>
        Login = 1,
        /// <summary>
        /// 退出
        /// </summary>
        Logout = 2,
        /// <summary>
        /// 聊天消息
        /// </summary>
        ChatInfo = 3,
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace WebSocketsServer
{
    public class MessageInfo
    {
        /// <summary>
        /// 唯一标识
        /// </summary>
        public Guid Identity { get; set; }
        /// <summary>
        /// 用户名
        /// </summary>
        public string UserName { get; set; }
        /// <summary>
        /// 消息类型
        /// </summary>
        public MessageType MsgType { get; set; }
        /// <summary>
        /// 发送信息
        /// </summary>
        public string Message { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace WebSocketsServer
{
    public class Logger
    {
        public bool LogEvents { get; set; }
        public Logger()
        {
            LogEvents = true;
        }
 
        public void Log(string Text)
        {
            if (LogEvents) Console.WriteLine(Text);
        }
    }
}

Program类的实现

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
/// <summary>
/// WebSocket服务端
/// </summary>
namespace WebSocketsServer
{
    class Program
    {
        static void Main(string[] args)
        {
            WebSocketServer server = new WebSocketServer();
            server.StartServer();
            Console.ReadKey();
        }
    }
}

HTML页面实现代码如下(客户端)

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>WebSocket聊天室</title>
    <style type="text/css">
        .container {
            font-family: "Courier New";
            width: 500px;
            height: 400px;
            overflow: auto;
            border: 1px solid black;
            padding: 8px;
            background-color: lightgray;
        }
 
        .LockOff {
            display: none;
            visibility: hidden;
        }
 
        .LockOn {
            display: block;
            visibility: visible;
            position: absolute;
            z-index: 999;
            top: 0px;
            left: 0px;
            width: 1024%;
            height: 768%;
            background-color: #ccc;
            text-align: center;
            padding-top: 20%;
            filter: alpha(opacity=75);
            opacity: 0.75;
        }
 
        .userName {
            color: white;
            font-size: 12px;
        }
 
        .chatLeft {
            display: inline-block;
            color: black;
            font-size: 14px;
            margin-left: 20px;
            padding: 3px;
            border: 1px solid #ccc;
            background-color: #fff;
            text-align: left;
            vertical-align: middle;
        }
 
        .chatRight {
            display: inline-block;
            color: white;
            font-size: 14px;
            padding: 3px;
            border: 1px solid #ccc;
            background-color: #9eea6a;
            text-align: left;
            vertical-align: middle;
        }
 
        .login {
            width: 100%;
            display: inline-block;
            text-align: center;
            color: #ffff33;
            font-size: 14px;
            font-weight: 700;
        }
 
        .logout {
            width: 100%;
            display: inline-block;
            text-align: center;
            color: #ffa31a;
            font-size: 14px;
        }
 
        .systemInfo {
            color: gray;
            font-size: 15px;
        }
 
        .error {
            width: 100%;
            display: inline-block;
            text-align: center;
            color: red;
            font-size: 16px;
            font-weight: 700;
        }
    </style>
 
</head>
<body>
    <div id="skm_LockPane" class="LockOff"></div>
    <form id="form1" runat="server">
        <h1>WebSocket 聊天室</h1>
        <div>
            按下连接按钮,会通过WebSocket发起一个到聊天浏览器的连接。
        </div>
        服务器地址: <input type="text" id="Connection" /> 用户名: <input type="text" id="txtName" value="陈先生" />
        <button id='ToggleConnection' type="button" onclick='ToggleConnectionClicked();'>连接</button>
        <input type="hidden" value="" id="identity" />
        <br />
        <br />
        <div id='LogContainer' class='container'>
        </div>
        <br />
        <div id='SendDataContainer'>
            <input type="text" id="DataToSend" size="68" />
            <button id='SendData' type="button" onclick='SendDataClicked();'>发送</button>
        </div>
        <br />
    </form>
 
    <script src="Scripts/jquery-3.3.1.min.js"></script>
    <script type="text/javascript">
        //webSocket对象
        var ws;
        //Socket是否创建
        var SocketCreated = false;
        //用户是否退出登录
        var isUserloggedout = false;
 
        //模拟用户唯一标识
        var identity = "";
        var userName = "";
        var LOGIN = 1, LOGOUT = 2, CHATINFO = 3, SYSYEMINFO = 4, ERROR = -1;
 
        function lockOn(str) {
            var lock = document.getElementById('skm_LockPane');
            if (lock)
                lock.className = 'LockOn';
            lock.innerHTML = str;
        }
 
        function lockOff() {
            var lock = document.getElementById('skm_LockPane');
            lock.className = 'LockOff';
        }
 
        function ToggleConnectionClicked() {
            userName = document.getElementById("txtName").value.trim();
            if (identity.trim() == "") {
                identity = newGuid();
            }
            //(连接尚未建立||连接已建立)
            if (SocketCreated && (ws.readyState == 0 || ws.readyState == 1)) {
                lockOn("离开聊天室...");
                SocketCreated = false;
                isUserloggedout = true;
                var data = MsgData(LOGOUT, "【" + userName + "】" + "离开了聊天室!");
                ws.send(JSON.stringify(data));
                ws.close();
            } else {
                lockOn("进入聊天室...");
                var data = MsgData(SYSYEMINFO, "准备连接到聊天服务器...");
                Log(data);
                try {
                    if ("WebSocket" in window) {
                        ws = new WebSocket("ws://" + document.getElementById("Connection").value);
                    }
                    else if ("MozWebSocket" in window) {
                        ws = new MozWebSocket("ws://" + document.getElementById("Connection").value);
                    }
                    SocketCreated = true;
                    isUserloggedout = false;
                } catch (ex) {
                    var data = MsgData(ERROR, ex);
                    Log(data);
                    return;
                }
                document.getElementById("ToggleConnection").innerHTML = "断开";
                ws.onopen = WSonOpen;
                ws.onmessage = WSonMessage;
                ws.onclose = WSonClose;
                ws.onerror = WSonError;
            }
        };
 
        //WebSocket打开事件
        function WSonOpen() {
            lockOff();
            var data = MsgData(SYSYEMINFO, "连接已经建立.");
            Log(data);
            $("#SendDataContainer").show();
            var data = MsgData(LOGIN, "欢迎【" + userName + "】来到聊天室!");
            ws.send(JSON.stringify(data));
        };
        //WebSocket接收消息事件
        function WSonMessage(event) {
            Log(event.data);
        };
        //WebSocket关闭连接事件
        function WSonClose() {
            lockOff();
            if (isUserloggedout) {
                var data = MsgData(LOGOUT, "【" + userName + "】" + "离开了聊天室!");
                Log(JSON.stringify(data));
            }
            document.getElementById("ToggleConnection").innerHTML = "连接";
            $("#SendDataContainer").hide();
        };
        //WebSocket发生错误
        function WSonError() {
            lockOff();
            var data = MsgData(ERROR, "远程连接中断...");
            Log(data);
        };
 
 
        function SendDataClicked() {
            if (document.getElementById("DataToSend").value.trim() != "") {
                var data = MsgData(CHATINFO, document.getElementById("DataToSend").value)
                ws.send(JSON.stringify(data));
                document.getElementById("DataToSend").value = "";
            }
        };
 
        //传递的消息对象
        function MsgData(MsgType, Message) {
            var data = new Object();
            data.Identity = identity;
            data.UserName = userName;
            data.MsgType = MsgType;
            data.Message = Message;
            return data;
        }
 
        function Log(data) {
            console.log(data);
            if (!(data.constructor === Object)) {
                data = JSON.parse(data);
            }
            var html = "";
            if (data.MsgType === CHATINFO) {
                if (data.Identity === identity) {
                    html = "<div style='display:inline-block;width:100%;text-align:right;margin-bottom:2px'>";
                    html += "<span class='chatRight'>" + data.Message + "</span>";
                    html += "</div>";
                }
                else {
                    html += "<span class='userName'>" + data.UserName + ":</span>";
                    html += "</br>";
                    html += "<span class='chatLeft'>" + data.Message + "</span>";
                }
            }
            else if (data.MsgType === LOGIN) {
                html = "<span class='login'>" + data.Message + "</span>"
            }
            else if (data.MsgType === LOGOUT) {
                html = "<span class='logout'>" + data.Message + "</span>"
            }
            else if (data.MsgType === SYSYEMINFO) {
                html += "<span class='systemInfo'>" + data.Message + "</span>";
            }
            else if (data.MsgType === ERROR) {
                html = "<span class='error'>" + data + "</span>";
            }
            document.getElementById("LogContainer").innerHTML = document.getElementById("LogContainer").innerHTML + html + "<br />";
            var LogContainer = document.getElementById("LogContainer");
            LogContainer.scrollTop = LogContainer.scrollHeight;
        };
        //JS生成GUID函数,类似.net中的NewID();
        function newGuid() {
            var guid = "";
            for (var i = 1; i <= 32; i++) {
                var n = Math.floor(Math.random() * 16.0).toString(16);
                guid += n;
                if ((i == 8) || (i == 12) || (i == 16) || (i == 20))
                    guid += "-";
            }
            return guid;
        }
 
        $(document).ready(function () {
            $("#SendDataContainer").hide();
            var WebSocketsExist = false;
            if ("WebSocket" in window) {
                WebSocketsExist = true;
            }
            if (WebSocketsExist) {
                var data = MsgData(SYSYEMINFO, "您的浏览器支持WebSocket. 您可以尝试连接到聊天服务器!");
                Log(data);
                document.getElementById("Connection").value = "192.168.137.1:9000";
            } else {
                var data = MsgData(ERROR, "您的浏览器不支持WebSocket。请选择其他的浏览器再尝试连接服务器。");
                Log(data);
                document.getElementById("ToggleConnection").disabled = true;
            }
 
            $("#DataToSend").keypress(function (evt) {
                if (evt.keyCode == 13) {
                    $("#SendData").click();
                    evt.preventDefault();
                }
            })
        });
 
    </script>
</body>
</html>

实现效果如图(打开两个HTML实现聊天功能)

控制台获取的信息如下

完结。

加载全部内容

相关教程
猜你喜欢
用户评论