#region MIT License /** * WebSocket.cs * * A C# implementation of the WebSocket interface. * This code derived from WebSocket.java (http://github.com/adamac/Java-WebSocket-client). * * The MIT License * * Copyright (c) 2009 Adam MacBeth * Copyright (c) 2010-2012 sta.blockhead * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #endregion using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.Net.Sockets; using System.Security.Cryptography; using System.Text; using System.Threading; using WebSocketSharp.Frame; using WebSocketSharp.Net; using WebSocketSharp.Net.Sockets; namespace WebSocketSharp { /// /// Implements the WebSocket interface. /// /// /// The WebSocket class provides methods and properties for two-way communication using the WebSocket protocol (RFC 6455). /// public class WebSocket : IDisposable { #region Private Const Fields private const int _fragmentLen = 1016; // Max value is int.MaxValue - 14. private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; private const string _version = "13"; #endregion #region Private Fields private string _base64key; private HttpListenerContext _httpContext; private WebSocketContext _context; private System.Net.IPEndPoint _endPoint; private string _extensions; private AutoResetEvent _exitMessageLoop; private Object _forClose; private Object _forSend; private bool _isClient; private bool _isSecure; private string _protocol; private string _protocols; private NameValueCollection _queryString; private volatile WsState _readyState; private AutoResetEvent _receivePong; private TcpClient _tcpClient; private Uri _uri; private SynchronizedCollection _unTransmittedBuffer; private WsStream _wsStream; private string _origin; #endregion #region Private Constructor private WebSocket() { _extensions = String.Empty; _forClose = new Object(); _forSend = new Object(); _protocol = String.Empty; _readyState = WsState.CONNECTING; _unTransmittedBuffer = new SynchronizedCollection(); } #endregion #region Internal Constructor internal WebSocket(HttpListenerWebSocketContext context) : this() { _uri = Ext.ToUri(context.Path); _context = context; _httpContext = context.BaseContext; _wsStream = context.Stream; _endPoint = context.ServerEndPoint; _isClient = false; _isSecure = context.IsSecureConnection; } internal WebSocket(TcpListenerWebSocketContext context) : this() { _uri = Ext.ToUri(context.Path); _context = context; _tcpClient = context.Client; _wsStream = context.Stream; _endPoint = context.ServerEndPoint; _isClient = false; _isSecure = context.IsSecureConnection; } #endregion #region Public Constructors /// /// Initializes a new instance of the class with the specified WebSocket URL and subprotocols. /// /// /// A that contains the WebSocket URL. /// /// /// An array of that contains the WebSocket subprotocols if any. /// /// /// is . /// /// /// is not valid WebSocket URL. /// public WebSocket(string url, params string[] protocols) : this() { if (url == null) throw new ArgumentNullException("url"); Uri uri; string msg; if (!tryCreateUri(url, out uri, out msg)) throw new ArgumentException(msg, "url"); _uri = uri; _protocols = Ext.ToString(protocols, ", "); _base64key = createBase64Key(); _isClient = true; _isSecure = uri.Scheme == "wss" ? true : false; } /// /// Initializes a new instance of the class with the specified WebSocket URL, OnOpen, OnMessage, OnError, OnClose event handlers and subprotocols. /// /// /// A that contains the WebSocket URL. /// /// /// An OnOpen event handler. /// /// /// An OnMessage event handler. /// /// /// An OnError event handler. /// /// /// An OnClose event handler. /// /// /// An array of that contains the WebSocket subprotocols if any. /// /// /// is . /// /// /// is not valid WebSocket URL. /// public WebSocket( string url, EventHandler onOpen, EventHandler onMessage, EventHandler onError, EventHandler onClose, params string[] protocols) : this(url, protocols) { OnOpen = onOpen; OnMessage = onMessage; OnError = onError; OnClose = onClose; Connect(); } #endregion #region Internal Property internal NameValueCollection QueryString { get { return _queryString; } } #endregion #region Public Properties /// /// Gets the amount of untransmitted data. /// /// /// The number of bytes of untransmitted data. /// public ulong BufferedAmount { get { lock (_unTransmittedBuffer.SyncRoot) { ulong bufferedAmount = 0; foreach (WsFrame frame in _unTransmittedBuffer) bufferedAmount += frame.PayloadLength; return bufferedAmount; } } } /// /// Gets the extensions selected by the server. /// /// /// A that contains the extensions if any. By default, String.Empty. (Currently this will only ever be the String.Empty.) /// public string Extensions { get { return _extensions; } } /// /// Gets a value indicating whether a connection is alive. /// /// /// true if the connection is alive; otherwise, false. /// public bool IsAlive { get { if (_readyState != WsState.OPEN) return false; return Ping(); } } /// /// Gets a value indicating whether a connection is secure. /// /// /// true if the connection is secure; otherwise, false. /// public bool IsSecure { get { return _isSecure; } } /// /// Gets the subprotocol selected by the server. /// /// /// A that contains the subprotocol if any. By default, String.Empty. /// public string Protocol { get { return _protocol; } } /// /// Gets the state of the connection. /// /// /// A . By default, WsState.CONNECTING. /// public WsState ReadyState { get { return _readyState; } } /// /// Gets the untransmitted WebSocket frames. /// /// /// A IList<WsFrame> that contains the untransmitted WebSocket frames. /// public IList UnTransmittedBuffer { get { return _unTransmittedBuffer; } } /// /// Gets or sets the WebSocket URL. /// /// /// A that contains the WebSocket URL. /// public Uri Url { get { return _uri; } set { if (_readyState == WsState.CONNECTING && !_isClient) _uri = value; } } /// /// Gets or sets the WebSocket Origin. /// public string Origin { get { return this._origin; } set { this._origin = value; } } /// /// Gets or sets the extra WebSocket handshake headers. /// public IDictionary ExtraHeaders { get; set; } #endregion #region Events /// /// Occurs when the WebSocket connection has been established. /// public event EventHandler OnOpen; /// /// Occurs when the WebSocket receives a data frame. /// public event EventHandler OnMessage; /// /// Occurs when the WebSocket gets an error. /// public event EventHandler OnError; /// /// Occurs when the WebSocket receives a Close frame or the Close method is called. /// public event EventHandler OnClose; #endregion #region Private Methods // As Server private void acceptHandshake() { var req = receiveOpeningHandshake(); string msg; if (!isValidRequest(req, out msg)) { onError(msg); close(CloseStatusCode.HANDSHAKE_FAILURE, msg); return; } sendResponseHandshake(); onOpen(); } private bool canSendAsCloseFrame(PayloadData data) { if (data.Length >= 2) { var code = Ext.To(Ext.SubArray(data.ToBytes(), 0, 2), ByteOrder.BIG); if (code == (ushort)CloseStatusCode.NO_STATUS_CODE || code == (ushort)CloseStatusCode.ABNORMAL || code == (ushort)CloseStatusCode.HANDSHAKE_FAILURE) return false; } return true; } private void close(HttpStatusCode code) { if (_readyState != WsState.CONNECTING || _isClient) return; sendResponseHandshake(code); closeConnection(); } private void close(PayloadData data) { #if DEBUG Console.WriteLine("WS: Info@close: Current thread IsBackground ?: {0}", Thread.CurrentThread.IsBackground); #endif lock (_forClose) { // Whether the closing handshake has been started already ? if (_readyState == WsState.CLOSING || _readyState == WsState.CLOSED) return; // Whether the closing handshake as server is started before the connection has been established ? if (_readyState == WsState.CONNECTING && !_isClient) { sendResponseHandshake(HttpStatusCode.BadRequest); onClose(new CloseEventArgs(data)); return; } _readyState = WsState.CLOSING; } // Whether a close status code that must not be set for send is used ? if (!canSendAsCloseFrame(data)) { onClose(new CloseEventArgs(data)); return; } closeHandshake(data); #if DEBUG Console.WriteLine("WS: Info@close: Exits close method."); #endif } private void close(CloseStatusCode code, string reason) { close((ushort)code, reason); } private void close(ushort code, string reason) { var data = new List(Ext.ToBytes(code, ByteOrder.BIG)); if (!Ext.IsNullOrEmpty(reason)) { var buffer = Encoding.UTF8.GetBytes(reason); data.AddRange(buffer); } var payloadData = new PayloadData(data.ToArray()); if (payloadData.Length > 125) { var msg = "A Close frame must have a payload length of 125 bytes or less."; onError(msg); return; } close(payloadData); } private bool closeConnection() { _readyState = WsState.CLOSED; try { if (_httpContext != null) { _httpContext.Response.Close(); _wsStream = null; _httpContext = null; } if (_wsStream != null) { _wsStream.Dispose(); _wsStream = null; } if (_tcpClient != null) { _tcpClient.Close(); _tcpClient = null; } return true; } catch (Exception ex) { onError(ex.Message); return false; } } private void closeHandshake(PayloadData data) { var args = new CloseEventArgs(data); var frame = createFrame(Fin.FINAL, Opcode.CLOSE, data); send(frame); onClose(args); } // As Client private string createBase64Key() { var src = new byte[16]; var rand = new Random(); rand.NextBytes(src); return Convert.ToBase64String(src); } // As Client private void createClientStream() { var host = _uri.DnsSafeHost; var port = _uri.Port > 0 ? _uri.Port : _isSecure ? 443 : 80; _tcpClient = new TcpClient(host, port); _wsStream = WsStream.CreateClientStream(_tcpClient, host, _isSecure); } private WsFrame createFrame(Fin fin, Opcode opcode, PayloadData payloadData) { return _isClient ? new WsFrame(fin, opcode, payloadData) : new WsFrame(fin, opcode, Mask.UNMASK, payloadData); } // As Client private RequestHandshake createOpeningHandshake() { var path = _uri.PathAndQuery; var host = _uri.DnsSafeHost; var port = ((System.Net.IPEndPoint)_tcpClient.Client.RemoteEndPoint).Port; if (port != 80) host += ":" + port; var req = new RequestHandshake(path); req.AddHeader("Host", host); req.AddHeader("Sec-WebSocket-Key", _base64key); if (!Ext.IsNullOrEmpty(_protocols)) req.AddHeader("Sec-WebSocket-Protocol", _protocols); req.AddHeader("Sec-WebSocket-Version", _version); if (!string.IsNullOrEmpty(this._origin)) req.AddHeader("Origin", this._origin); //extra headers if (this.ExtraHeaders != null) foreach (var i in this.ExtraHeaders) req.AddHeader(i.Key, i.Value); return req; } // As Server private ResponseHandshake createResponseHandshake() { var res = new ResponseHandshake(); res.AddHeader("Sec-WebSocket-Accept", createResponseKey()); return res; } // As Server private ResponseHandshake createResponseHandshake(HttpStatusCode code) { var res = ResponseHandshake.CreateCloseResponse(code); res.AddHeader("Sec-WebSocket-Version", _version); return res; } private string createResponseKey() { SHA1 sha1 = new SHA1CryptoServiceProvider(); var sb = new StringBuilder(_base64key); sb.Append(_guid); var src = sha1.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString())); return Convert.ToBase64String(src); } // As Client private void doHandshake() { var res = sendOpeningHandshake(); string msg; if (!isValidResponse(res, out msg)) { onError(msg); close(CloseStatusCode.HANDSHAKE_FAILURE, msg); return; } onOpen(); } private bool isValidCloseStatusCode(ushort code, out string message) { if (code < 1000) { message = "Close status codes in the range 0-999 are not used: " + code; return false; } if (code > 4999) { message = "Out of reserved close status code range: " + code; return false; } message = String.Empty; return true; } private bool isValidFrame(WsFrame frame) { if (frame == null) { var msg = "The WebSocket frame can not be read from the network stream."; close(CloseStatusCode.ABNORMAL, msg); return false; } return true; } // As Server private bool isValidRequest(RequestHandshake request, out string message) { if (!request.IsWebSocketRequest) { message = "Invalid WebSocket request."; return false; } if (_uri.IsAbsoluteUri && !isValidRequestHost(request.Headers["Host"], out message)) return false; if (!request.HeaderExists("Sec-WebSocket-Version", _version)) { message = "Unsupported Sec-WebSocket-Version."; return false; } _base64key = request.Headers["Sec-WebSocket-Key"]; if (request.HeaderExists("Sec-WebSocket-Protocol")) _protocols = request.Headers["Sec-WebSocket-Protocol"]; if (request.HeaderExists("Sec-WebSocket-Extensions")) _extensions = request.Headers["Sec-WebSocket-Extensions"]; _queryString = request.QueryString; message = String.Empty; return true; } // As Server private bool isValidRequestHost(string value, out string message) { var host = _uri.DnsSafeHost; var type = Uri.CheckHostName(host); var address = _endPoint.Address; var port = _endPoint.Port; var expectedHost1 = host; var expectedHost2 = type == UriHostNameType.Dns ? address.ToString() : System.Net.Dns.GetHostEntry(address).HostName; if (port != 80) { expectedHost1 += ":" + port; expectedHost2 += ":" + port; } if (Ext.NotEqual(expectedHost1, value, false) && Ext.NotEqual(expectedHost2, value, false)) { message = "Invalid Host."; return false; } message = String.Empty; return true; } // As Client private bool isValidResponse(ResponseHandshake response, out string message) { if (!response.IsWebSocketResponse) { message = "Invalid WebSocket response."; return false; } if (!response.HeaderExists("Sec-WebSocket-Accept", createResponseKey())) { message = "Invalid Sec-WebSocket-Accept."; return false; } if (response.HeaderExists("Sec-WebSocket-Version") && !response.HeaderExists("Sec-WebSocket-Version", _version)) { message = "Unsupported Sec-WebSocket-Version."; return false; } if (response.HeaderExists("Sec-WebSocket-Protocol")) _protocol = response.Headers["Sec-WebSocket-Protocol"]; if (response.HeaderExists("Sec-WebSocket-Extensions")) _extensions = response.Headers["Sec-WebSocket-Extensions"]; message = String.Empty; return true; } private void onClose(CloseEventArgs eventArgs) { if (!Thread.CurrentThread.IsBackground) if (_exitMessageLoop != null) _exitMessageLoop.WaitOne(5 * 1000, false); if (closeConnection()) eventArgs.WasClean = true; Ext.Emit(OnClose, this, eventArgs); } private void onError(string message) { #if DEBUG var callerFrame = new StackFrame(1); var caller = callerFrame.GetMethod(); Console.WriteLine("WS: Error@{0}: {1}", caller.Name, message); #endif Ext.Emit(OnError, this, new ErrorEventArgs(message)); } private void onMessage(MessageEventArgs eventArgs) { if (eventArgs != null) Ext.Emit(OnMessage, this, eventArgs); } private void onOpen() { _readyState = WsState.OPEN; startMessageLoop(); Ext.Emit(OnOpen, this, EventArgs.Empty); } private bool ping(string message, int millisecondsTimeout) { var buffer = Encoding.UTF8.GetBytes(message); if (buffer.Length > 125) { var msg = "A Ping frame must have a payload length of 125 bytes or less."; onError(msg); return false; } if (!send(Fin.FINAL, Opcode.PING, buffer)) return false; return _receivePong.WaitOne(millisecondsTimeout, false); } private void pong(PayloadData data) { var frame = createFrame(Fin.FINAL, Opcode.PONG, data); send(frame); } private void pong(string data) { var payloadData = new PayloadData(data); pong(payloadData); } private WsFrame readFrame() { var frame = _wsStream.ReadFrame(); return isValidFrame(frame) ? frame : null; } private string[] readHandshake() { return _wsStream.ReadHandshake(); } private MessageEventArgs receive(WsFrame frame) { if (!isValidFrame(frame)) return null; if ((frame.Fin == Fin.FINAL && frame.Opcode == Opcode.CONT) || (frame.Fin == Fin.MORE && frame.Opcode == Opcode.CONT)) return null; if (frame.Fin == Fin.MORE) {// MORE var merged = receiveFragmented(frame); return merged != null ? new MessageEventArgs(frame.Opcode, new PayloadData(merged)) : null; } if (frame.Opcode == Opcode.CLOSE) {// FINAL & CLOSE #if DEBUG Console.WriteLine("WS: Info@receive: Starts closing handshake."); #endif close(frame.PayloadData); return null; } if (frame.Opcode == Opcode.PING) {// FINAL & PING #if DEBUG Console.WriteLine("WS: Info@receive: Returns Pong."); #endif pong(frame.PayloadData); return null; } if (frame.Opcode == Opcode.PONG) {// FINAL & PONG #if DEBUG Console.WriteLine("WS: Info@receive: Receives Pong."); #endif _receivePong.Set(); return null; } // FINAL & (TEXT | BINARY) return new MessageEventArgs(frame.Opcode, frame.PayloadData); } private byte[] receiveFragmented(WsFrame firstFrame) { var buffer = new List(firstFrame.PayloadData.ToBytes()); while (true) { var frame = readFrame(); if (frame == null) return null; if (frame.Fin == Fin.MORE) { if (frame.Opcode == Opcode.CONT) {// MORE & CONT buffer.AddRange(frame.PayloadData.ToBytes()); continue; } #if DEBUG Console.WriteLine("WS: Info@receiveFragmented: Starts closing handshake."); #endif close(CloseStatusCode.INCORRECT_DATA, String.Empty); return null; } if (frame.Opcode == Opcode.CONT) {// FINAL & CONT buffer.AddRange(frame.PayloadData.ToBytes()); break; } if (frame.Opcode == Opcode.CLOSE) {// FINAL & CLOSE #if DEBUG Console.WriteLine("WS: Info@receiveFragmented: Starts closing handshake."); #endif close(frame.PayloadData); return null; } if (frame.Opcode == Opcode.PING) {// FINAL & PING #if DEBUG Console.WriteLine("WS: Info@receiveFragmented: Returns Pong."); #endif pong(frame.PayloadData); continue; } if (frame.Opcode == Opcode.PONG) {// FINAL & PONG #if DEBUG Console.WriteLine("WS: Info@receiveFragmented: Receives Pong."); #endif _receivePong.Set(); continue; } // FINAL & (TEXT | BINARY) #if DEBUG Console.WriteLine("WS: Info@receiveFragmented: Starts closing handshake."); #endif close(CloseStatusCode.INCORRECT_DATA, String.Empty); return null; } return buffer.ToArray(); } // As Server private RequestHandshake receiveOpeningHandshake() { var req = RequestHandshake.Parse(_context); #if DEBUG Console.WriteLine("WS: Info@receiveOpeningHandshake: Opening handshake from client:\n"); Console.WriteLine(req.ToString()); #endif return req; } // As Client private ResponseHandshake receiveResponseHandshake() { var res = ResponseHandshake.Parse(readHandshake()); #if DEBUG Console.WriteLine("WS: Info@receiveResponseHandshake: Response handshake from server:\n"); Console.WriteLine(res.ToString()); #endif return res; } private bool send(WsFrame frame) { if (_readyState == WsState.CONNECTING || _readyState == WsState.CLOSED) { var msg = "The WebSocket connection isn't established or has been closed."; onError(msg); return false; } try { if (_unTransmittedBuffer.Count == 0) { if (_wsStream != null) { _wsStream.WriteFrame(frame); return true; } } if (_unTransmittedBuffer.Count > 0) { _unTransmittedBuffer.Add(frame); var msg = "Current data can not be sent because there is untransmitted data."; onError(msg); } return false; } catch (Exception ex) { _unTransmittedBuffer.Add(frame); onError(ex.Message); return false; } } private void send(Opcode opcode, byte[] data) { using (MemoryStream ms = new MemoryStream(data)) { send(opcode, ms); } } private void send(Opcode opcode, Stream stream) { lock (_forSend) { try { if (_readyState != WsState.OPEN) { var msg = "The WebSocket connection isn't established or has been closed."; onError(msg); return; } var length = stream.Length; if (length <= _fragmentLen) send(Fin.FINAL, opcode, Ext.ReadBytes(stream, (int)length)); else sendFragmented(opcode, stream); } catch (Exception ex) { onError(ex.Message); } } } private bool send(Fin fin, Opcode opcode, byte[] data) { var frame = createFrame(fin, opcode, new PayloadData(data)); return send(frame); } private void sendAsync(Opcode opcode, byte[] data, Action completed) { sendAsync(opcode, new MemoryStream(data), completed); } private void sendAsync(Opcode opcode, Stream stream, Action completed) { Action action = send; AsyncCallback callback = (ar) => { try { action.EndInvoke(ar); if (completed != null) completed(); } catch (Exception ex) { onError(ex.Message); } finally { stream.Close(); } }; action.BeginInvoke(opcode, stream, callback, null); } private long sendFragmented(Opcode opcode, Stream stream) { var length = stream.Length; var quo = length / _fragmentLen; var rem = length % _fragmentLen; var count = rem == 0 ? quo - 2 : quo - 1; // First var buffer = new byte[_fragmentLen]; long readLen = stream.Read(buffer, 0, _fragmentLen); send(Fin.MORE, opcode, buffer); // Mid Ext.Times(count, () => { readLen += stream.Read(buffer, 0, _fragmentLen); send(Fin.MORE, Opcode.CONT, buffer); }); // Final if (rem != 0) buffer = new byte[rem]; readLen += stream.Read(buffer, 0, buffer.Length); send(Fin.FINAL, Opcode.CONT, buffer); return readLen; } // As Client private ResponseHandshake sendOpeningHandshake() { var req = createOpeningHandshake(); sendOpeningHandshake(req); return receiveResponseHandshake(); } // As Client private void sendOpeningHandshake(RequestHandshake request) { #if DEBUG Console.WriteLine("WS: Info@sendOpeningHandshake: Opening handshake from client:\n"); Console.WriteLine(request.ToString()); #endif writeHandshake(request); } // As Server private void sendResponseHandshake() { var res = createResponseHandshake(); sendResponseHandshake(res); } // As Server private void sendResponseHandshake(HttpStatusCode code) { var res = createResponseHandshake(code); sendResponseHandshake(res); } // As Server private void sendResponseHandshake(ResponseHandshake response) { #if DEBUG Console.WriteLine("WS: Info@sendResponseHandshake: Response handshake from server:\n"); Console.WriteLine(response.ToString()); #endif writeHandshake(response); } private void startMessageLoop() { _exitMessageLoop = new AutoResetEvent(false); _receivePong = new AutoResetEvent(false); Action completed = null; completed = (frame) => { try { onMessage(receive(frame)); if (_readyState == WsState.OPEN) _wsStream.ReadFrameAsync(completed); else _exitMessageLoop.Set(); } catch (WsReceivedTooBigMessageException ex) { close(CloseStatusCode.TOO_BIG, ex.Message); } catch (Exception ex) { //HACK:close with 1006 when onMessage exception? close(CloseStatusCode.ABNORMAL , string.Format("An exception has occured: {0}", ex.Message)); } }; _wsStream.ReadFrameAsync(completed); } private bool tryCreateUri(string uriString, out Uri result, out string message) { return Ext.TryCreateWebSocketUri(uriString, out result, out message); } private void writeHandshake(Handshake handshake) { _wsStream.WriteHandshake(handshake); } #endregion #region Internal Method // As Server internal void Close(HttpStatusCode code) { close(code); } #endregion #region Public Methods /// /// Closes the connection and releases all associated resources after sends a Close control frame. /// public void Close() { var data = new PayloadData(new byte[] { }); close(data); } /// /// Closes the connection and releases all associated resources after sends a Close control frame. /// /// /// A that contains a status code indicating a reason for closure. /// public void Close(CloseStatusCode code) { Close(code, String.Empty); } /// /// Closes the connection and releases all associated resources after sends a Close control frame. /// /// /// A that contains a status code indicating a reason for closure. /// public void Close(ushort code) { Close(code, String.Empty); } /// /// Closes the connection and releases all associated resources after sends a Close control frame. /// /// /// A that contains a status code indicating a reason for closure. /// /// /// A that contains a reason for closure. /// public void Close(CloseStatusCode code, string reason) { Close((ushort)code, reason); } /// /// Closes the connection and releases all associated resources after sends a Close control frame. /// /// /// A that contains a status code indicating a reason for closure. /// /// /// A that contains a reason for closure. /// public void Close(ushort code, string reason) { string msg; if (!isValidCloseStatusCode(code, out msg)) { onError(msg); return; } close(code, reason); } /// /// Establishes a connection. /// public void Connect() { if (_readyState == WsState.OPEN) { Console.WriteLine("WS: Info@Connect: The WebSocket connection has been established already."); return; } try { // As client if (_isClient) { createClientStream(); doHandshake(); return; } // As server acceptHandshake(); } catch (Exception ex) { onError(ex.Message); close(CloseStatusCode.HANDSHAKE_FAILURE, "An exception has occured."); } } /// /// Closes the connection and releases all associated resources after sends a Close control frame. /// /// /// Call when you are finished using the . The /// method leaves the in an unusable state. After /// calling , you must release all references to the so /// the garbage collector can reclaim the memory that the was occupying. /// public void Dispose() { Close(CloseStatusCode.AWAY); } /// /// Sends a Ping frame using the connection. /// /// /// true if the WebSocket receives a Pong frame in a time; otherwise, false. /// public bool Ping() { return Ping(String.Empty); } /// /// Sends a Ping frame with a message using the connection. /// /// /// A that contains the message to be sent. /// /// /// true if the WebSocket receives a Pong frame in a time; otherwise, false. /// public bool Ping(string message) { if (message == null) message = String.Empty; return _isClient ? ping(message, 5 * 1000) : ping(message, 1 * 1000); } /// /// Sends a text data using the connection. /// /// /// A that contains the text data to be sent. /// public void Send(string data) { if (data == null) { onError("'data' must not be null."); return; } var buffer = Encoding.UTF8.GetBytes(data); send(Opcode.TEXT, buffer); } /// /// Sends a binary data using the connection. /// /// /// An array of that contains the binary data to be sent. /// public void Send(byte[] data) { if (data == null) { onError("'data' must not be null."); return; } send(Opcode.BINARY, data); } /// /// Sends a binary data using the connection. /// /// /// A that contains the binary data to be sent. /// public void Send(FileInfo file) { if (file == null) { onError("'file' must not be null."); return; } using (FileStream fs = file.OpenRead()) { send(Opcode.BINARY, fs); } } /// /// Sends a text data asynchronously using the connection. /// /// /// A that contains the text data to be sent. /// /// /// An delegate that contains the method(s) that is called when an asynchronous operation completes. /// public void SendAsync(string data, Action completed) { if (data == null) { onError("'data' must not be null."); return; } var buffer = Encoding.UTF8.GetBytes(data); sendAsync(Opcode.TEXT, buffer, completed); } /// /// Sends a binary data asynchronously using the connection. /// /// /// An array of that contains the binary data to be sent. /// /// /// An delegate that contains the method(s) that is called when an asynchronous operation completes. /// public void SendAsync(byte[] data, Action completed) { if (data == null) { onError("'data' must not be null."); return; } sendAsync(Opcode.BINARY, data, completed); } /// /// Sends a binary data asynchronously using the connection. /// /// /// A that contains the binary data to be sent. /// /// /// An delegate that contains the method(s) that is called when an asynchronous operation completes. /// public void SendAsync(FileInfo file, Action completed) { if (file == null) { onError("'file' must not be null."); return; } sendAsync(Opcode.BINARY, file.OpenRead(), completed); } #endregion } }