C#使用WebSocket实现聊天室功能

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实现聊天功能)

控制台获取的信息如下

完结。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • C#实现简易多人聊天室

    本文实例为大家分享了C#实现简易多人聊天室的具体代码,供大家参考,具体内容如下 只有一个群聊的功能 服务端 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Net; using System.Net.Sockets; using System.Te

  • c#基于WinForm的Socket实现简单的聊天室 IM

    1:什么是Socket 所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象. 一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制. 从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口. 2:客服端和服务端的通信简单流程 3:服务端Code: using System; using System.Collections.Generic; using Sys

  • C#简单聊天室雏形

    本文实例为大家分享了C#简单聊天室雏形的具体代码,供大家参考,具体内容如下 程序使用的控制台的黑窗口模拟程序,第一次涉及网络编程,写出来方便以后查阅,代码很简单首先是服务器端的代码: public class ServerControl     {         private Socket serverSocket;         public ServerControl()         {             serverSocket = new Socket(AddressFa

  • C#制作简单的多人在线即时交流聊天室

    实现网页版的在线聊天室的方法有很多,在没有来到HTML5之前,常见的有:定时轮询.长连接+长轮询.基于第三方插件(如FLASH的Socket),而如果是HTML5,则比较简单,可以直接使用WebSocket,当然HTML5目前在PC端并没有被所有浏览器支持,所以我的这个聊天室仍是基于长连接+长轮询+原生的JS及AJAX实现的多人在线即时交流聊天室,这个聊天室其实是我上周周末完成的,功能简单,可能有些不足,但可以满足在线即时聊天需求,分享也是给大家提供一个思路,大家可以基于此来实现更好的在线即时聊

  • C#使用Socket实现本地多人聊天室

    本文实例为大家分享了C#使用Socket实现本地多人聊天室的具体代码,供大家参考,具体内容如下 [脚本一:Server端] 使用本机地址:127.0.0.1 完整代码 using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading;   namespace ConsoleApp1 {     p

  • C#基于WebSocket实现聊天室功能

    本文实例为大家分享了C#基于WebSocket实现聊天室功能的具体代码,供大家参考,具体内容如下 前面两篇温习了,C# Socket内容 本章根据Socket异步聊天室修改成WebSocket聊天室 WebSocket特别的地方是 握手和消息内容的编码.解码(添加了ServerHelper协助处理) ServerHelper: using System; using System.Collections; using System.Text; using System.Security.Cryp

  • Django实现WebSocket在线聊天室功能(channels库)

    1.Django实现WebSocket在线聊天室 1.1 安装 pip install channels==2.3 (saas) F:\Desktop\Python_Study\CHS-Tracer\saas>pip install channels==2.3 Looking in indexes: http://mirrors.aliyun.com/pypi/simple/ Collecting channels==2.3   Downloading   ... Successfully in

  • C#使用WebSocket实现聊天室功能

    WebSocket介绍 WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议. 在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道.两者之间就直接可以数据互相传送. 浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据. 当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发

  • php+websocket 实现的聊天室功能详解

    本文实例讲述了php+websocket 实现的聊天室功能.分享给大家供大家参考,具体如下: 一.配置  开启socket组建,否则会报 Fatal error: Call to undefined function socket_create() 错误 1.打开php.ini配置文件,搜索 extension=php_sockets.dll,把前面的':'分号删掉.修改之后重启服务. 注意:如果php版本多,一定要注意使用的哪个版本就要取修改哪个版本的php.ini文件,wamp开启socke

  • Nodejs实现多房间简易聊天室功能

    1.前端界面代码 前端不是重点,够用就行,下面是前端界面,具体代码可到github下载. 2.服务器端搭建 本服务器需要提供两个功能:http服务和websocket服务,由于node的事件驱动机制,可将两种服务搭建在同一个端口下. 1.包描述文件:package.json,这里用到了两个依赖项,mime:确定静态文件mime类型,socket.io:搭建websocket服务,然后使用npm install  安装依赖 { "name": "chat_room",

  • Android使用Websocket实现聊天室

    最近的项目中要实现一个聊天的功能,类似于斗鱼TV的聊天室功能,与服务器端人商量后决定用WebSocket来做,但是在这之前我只知道Socket但是听都没有听过WebSocket,但是查看了相关的材料以后发现实现一个聊天室其实是很简单的!下面我们先来看看WebSocket. Autobahn|Android 是由Autobahn开发一个开源的Java/Android网络库,实现了WebSocket协议和Web应用程序消息传输协议来创建本地移动的WebSocket/ WAMP的客服端. WebSoc

  • 基于django channel实现websocket的聊天室的方法示例

    websocket 网易聊天室? ​ web微信? ​ 直播? 假如你工作以后,你的老板让你来开发一个内部的微信程序,你需要怎么办?我们先来分析一下里面的技术难点 消息的实时性? 实现群聊 现在有这样一个需求,老板给到你了,关乎你是否能转正?你要怎么做? 我们先说消息的实时性,按照我们目前的想法是我需要用http协议来做,那么http协议怎么来做那? 是不是要一直去访问我们的服务器,问服务器有没有人给我发消息,有没有人给我发消息?那么大家认为我多长时间去访问一次服务比较合适那? 1分钟1次?1分

  • php+html5基于websocket实现聊天室的方法

    本文实例讲述了php+html5基于websocket实现聊天室的方法.分享给大家供大家参考.具体如下: html5的websocket 实现了双向通信,折腾了几天弄了个聊天室,分享给大家 <?php error_reporting(E_ALL); ob_implicit_flush(); $sk=new Sock('127.0.0.1',8000); $sk->run(); class Sock{ public $sockets; public $users; public $master;

  • java实现一个简单TCPSocket聊天室功能分享

    本文实例为大家分享了java实现TCPSocket聊天室功能的相关代码,供大家参考,具体内容如下 1.TCPserver.java import java.net.*; import java.io.*; import java.util.*; import java.util.concurrent.*; public class TCPserver{ private static final int SERVERPORT = 8888; private ServerSocket MyServe

  • Redis实现多人多聊天室功能

    本文为大家分享了Redis支持多人多聊天室功能的设计代码,供大家参考,具体内容如下 设计原理 左边的一个数据域,代表两个聊天室,聊天室id分别是827,729 在聊天室827里,有2个人,分别是jason22,jeff24他们分别已经阅读过聊天室内的id为5和6的消息 右边的一个数据域,代表了用户在不同的聊天室,jason22参与了827与729聊天室,在这两个聊天室里,他分别阅读到了id为5和id为10的消息 另外827聊天室内id为5的消息与729聊天室内id为5的消息不一样. 同时还有三个

随机推荐