<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
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。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45