首頁 > 軟體

C#使用WebSocket實現聊天室功能

2022-02-11 13:02:12

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實現聊天功能)

控制檯獲取的資訊如下

完結。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com