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 { ////// 声明新连接处理事件 /// /// /// public delegate void NewConnection_EventHandler(string loginName, EventArgs args); ////// 声明接收数据处理事件 /// /// /// /// public delegate void DataReceive_EventHandler(object sender, string message, EventArgs args); ////// 声明断开连接处理事件 /// /// /// 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 { ////// Socket服务端 /// public class WebSocketServer : IDisposable { #region 私有变量 ////// ip /// private string _ip = string.Empty; ////// 端口 /// private int _port = 0; ////// 服务器地址 /// private string _serverLocation = string.Empty; ////// Socket对象 /// private Socket _socket = null; ////// 监听的最大连接数 /// private int maxListenConnect = 10; ////// 是否关闭Socket对象 /// private bool isDisposed = false; private Logger logger = null; ////// buffer缓存区字节数 /// private int maxBufferSize = 0; ////// 第一个字节,以0x00开始 /// private byte[] FirstByte; ////// 最后一个字节,以0xFF结束 /// private byte[] LastByte; #endregion #region 声明Socket处理事件 ////// Socket新连接事件 /// public event NewConnection_EventHandler NewConnectionHandler; ////// Socket接收消息事件 /// public event DataReceive_EventHandler DataReceiveHandler; ////// Socket断开连接事件 /// public event Disconncetion_EventHandler DisconnectionHandler; #endregion ////// 存放SocketConnection集合 /// ListSocketConnections = new List (); #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 /// /// 初始化私有变量 /// 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; } ////// 开启服务 /// 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); } } ////// 监听客户端连接 /// 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); } } ////// SocketConnection监听的新连接事件 /// /// /// private void SocketConnection_NewConnectionHandler(string loginName, EventArgs args) { NewConnectionHandler?.Invoke(loginName, EventArgs.Empty); } ////// SocketConnection监听的消息接收事件 /// /// /// /// private void SocketConnection_DataReceiveHandler(object sender, string msgData, EventArgs args) { //新用户连接进来时显示欢迎信息 //SocketConnection socketConnection = sender as SocketConnection; Send(msgData); } ////// SocketConnection监听的断开连接事件 /// /// /// private void SocketConnection_DisconnectionHandler(object sender, string message, EventArgs args) { if (sender is SocketConnection socket) { Send(message); socket.ConnectionSocket.Close(); SocketConnections.Remove(socket); } } ////// 发送消息 /// /// 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); } } } ////// 获取当前主机的IP地址 /// ///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 { ////// Socket成功建立的连接 /// public class SocketConnection { ////// 新的Socket连接 /// public Socket ConnectionSocket = null; #region Socket监听事件 ////// 新连接事件 /// public event NewConnection_EventHandler NewConnectionHandler; ////// 数据接收事件 /// public event DataReceive_EventHandler DataReceiveHandler; ////// 断开连接事件 /// 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; } ////// 最大缓存区字节数 /// private int maxBufferSize = 0; ////// 握手协议信息 /// private string handshake = string.Empty; ////// 握手协议信息(new) /// private string newHandshake = string.Empty; ////// 接收消息的数据缓存区 /// 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 ////// 初始化变量 /// 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; } ////// 处理异步接收消息回调方法 /// /// 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); } } ////// 由服务端像客户端发送消息完成回调 /// /// 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, ////// 登录 /// Login = 1, ////// 退出 /// Logout = 2, ////// 聊天消息 /// ChatInfo = 3, } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WebSocketsServer { public class MessageInfo { ////// 唯一标识 /// public Guid Identity { get; set; } ////// 用户名 /// public string UserName { get; set; } ////// 消息类型 /// public MessageType MsgType { get; set; } ////// 发送信息 /// 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; ////// WebSocket服务端 /// namespace WebSocketsServer { class Program { static void Main(string[] args) { WebSocketServer server = new WebSocketServer(); server.StartServer(); Console.ReadKey(); } } }
HTML页面实现代码如下(客户端)
WebSocket聊天室
实现效果如图(打开两个HTML实现聊天功能)
控制台获取的信息如下
完结。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。