| #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 | 
| { | 
|   | 
|     /// <summary> | 
|     /// Implements the WebSocket interface. | 
|     /// </summary> | 
|     /// <remarks> | 
|     /// The WebSocket class provides methods and properties for two-way communication using the WebSocket protocol (RFC 6455). | 
|     /// </remarks> | 
|     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<WsFrame> _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<WsFrame>(); | 
|         } | 
|   | 
|         #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 | 
|   | 
|         /// <summary> | 
|         /// Initializes a new instance of the <see cref="WebSocketSharp.WebSocket"/> class with the specified WebSocket URL and subprotocols. | 
|         /// </summary> | 
|         /// <param name="url"> | 
|         /// A <see cref="string"/> that contains the WebSocket URL. | 
|         /// </param> | 
|         /// <param name="protocols"> | 
|         /// An array of <see cref="string"/> that contains the WebSocket subprotocols if any. | 
|         /// </param> | 
|         /// <exception cref="ArgumentNullException"> | 
|         /// <paramref name="url"/> is <see langword="null"/>. | 
|         /// </exception> | 
|         /// <exception cref="ArgumentException"> | 
|         /// <paramref name="url"/> is not valid WebSocket URL. | 
|         /// </exception> | 
|         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; | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Initializes a new instance of the <see cref="WebSocketSharp.WebSocket"/> class with the specified WebSocket URL, OnOpen, OnMessage, OnError, OnClose event handlers and subprotocols. | 
|         /// </summary> | 
|         /// <param name="url"> | 
|         /// A <see cref="string"/> that contains the WebSocket URL. | 
|         /// </param> | 
|         /// <param name="onOpen"> | 
|         /// An OnOpen event handler. | 
|         /// </param> | 
|         /// <param name="onMessage"> | 
|         /// An OnMessage event handler. | 
|         /// </param> | 
|         /// <param name="onError"> | 
|         /// An OnError event handler. | 
|         /// </param> | 
|         /// <param name="onClose"> | 
|         /// An OnClose event handler. | 
|         /// </param> | 
|         /// <param name="protocols"> | 
|         /// An array of <see cref="string"/> that contains the WebSocket subprotocols if any. | 
|         /// </param> | 
|         /// <exception cref="ArgumentNullException"> | 
|         /// <paramref name="url"/> is <see langword="null"/>. | 
|         /// </exception> | 
|         /// <exception cref="ArgumentException"> | 
|         /// <paramref name="url"/> is not valid WebSocket URL. | 
|         /// </exception> | 
|         public WebSocket( | 
|           string url, | 
|           EventHandler onOpen, | 
|           EventHandler<MessageEventArgs> onMessage, | 
|           EventHandler<ErrorEventArgs> onError, | 
|           EventHandler<CloseEventArgs> 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 | 
|   | 
|         /// <summary> | 
|         /// Gets the amount of untransmitted data. | 
|         /// </summary> | 
|         /// <value> | 
|         /// The number of bytes of untransmitted data. | 
|         /// </value> | 
|         public ulong BufferedAmount | 
|         { | 
|             get | 
|             { | 
|                 lock (_unTransmittedBuffer.SyncRoot) | 
|                 { | 
|                     ulong bufferedAmount = 0; | 
|                     foreach (WsFrame frame in _unTransmittedBuffer) | 
|                         bufferedAmount += frame.PayloadLength; | 
|   | 
|                     return bufferedAmount; | 
|                 } | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Gets the extensions selected by the server. | 
|         /// </summary> | 
|         /// <value> | 
|         /// A <see cref="string"/> that contains the extensions if any. By default, <c>String.Empty</c>. (Currently this will only ever be the <c>String.Empty</c>.) | 
|         /// </value> | 
|         public string Extensions | 
|         { | 
|             get | 
|             { | 
|                 return _extensions; | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Gets a value indicating whether a connection is alive. | 
|         /// </summary> | 
|         /// <value> | 
|         /// <c>true</c> if the connection is alive; otherwise, <c>false</c>. | 
|         /// </value> | 
|         public bool IsAlive | 
|         { | 
|             get | 
|             { | 
|                 if (_readyState != WsState.OPEN) | 
|                     return false; | 
|   | 
|                 return Ping(); | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Gets a value indicating whether a connection is secure. | 
|         /// </summary> | 
|         /// <value> | 
|         /// <c>true</c> if the connection is secure; otherwise, <c>false</c>. | 
|         /// </value> | 
|         public bool IsSecure | 
|         { | 
|             get | 
|             { | 
|                 return _isSecure; | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Gets the subprotocol selected by the server. | 
|         /// </summary> | 
|         /// <value> | 
|         /// A <see cref="string"/> that contains the subprotocol if any. By default, <c>String.Empty</c>. | 
|         /// </value> | 
|         public string Protocol | 
|         { | 
|             get | 
|             { | 
|                 return _protocol; | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Gets the state of the connection. | 
|         /// </summary> | 
|         /// <value> | 
|         /// A <see cref="WebSocketSharp.WsState"/>. By default, <c>WsState.CONNECTING</c>. | 
|         /// </value> | 
|         public WsState ReadyState | 
|         { | 
|             get | 
|             { | 
|                 return _readyState; | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Gets the untransmitted WebSocket frames. | 
|         /// </summary> | 
|         /// <value> | 
|         /// A <c>IList<WsFrame></c> that contains the untransmitted WebSocket frames. | 
|         /// </value> | 
|         public IList<WsFrame> UnTransmittedBuffer | 
|         { | 
|             get | 
|             { | 
|                 return _unTransmittedBuffer; | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Gets or sets the WebSocket URL. | 
|         /// </summary> | 
|         /// <value> | 
|         /// A <see cref="Uri"/> that contains the WebSocket URL. | 
|         /// </value> | 
|         public Uri Url | 
|         { | 
|             get { return _uri; } | 
|             set | 
|             { | 
|                 if (_readyState == WsState.CONNECTING && !_isClient) | 
|                     _uri = value; | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Gets or sets the WebSocket Origin. | 
|         /// </summary> | 
|         public string Origin | 
|         { | 
|             get { return this._origin; } | 
|             set { this._origin = value; } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Gets or sets the extra WebSocket handshake headers. | 
|         /// </summary> | 
|         public IDictionary<string, string> ExtraHeaders { get; set; } | 
|   | 
|         #endregion | 
|   | 
|         #region Events | 
|   | 
|         /// <summary> | 
|         /// Occurs when the WebSocket connection has been established. | 
|         /// </summary> | 
|         public event EventHandler OnOpen; | 
|   | 
|         /// <summary> | 
|         /// Occurs when the WebSocket receives a data frame. | 
|         /// </summary> | 
|         public event EventHandler<MessageEventArgs> OnMessage; | 
|   | 
|         /// <summary> | 
|         /// Occurs when the WebSocket gets an error. | 
|         /// </summary> | 
|         public event EventHandler<ErrorEventArgs> OnError; | 
|   | 
|         /// <summary> | 
|         /// Occurs when the WebSocket receives a Close frame or the Close method is called. | 
|         /// </summary> | 
|         public event EventHandler<CloseEventArgs> 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<ushort>(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<byte>(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<byte>(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<Opcode, Stream> 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<WsFrame> 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 | 
|   | 
|         /// <summary> | 
|         /// Closes the connection and releases all associated resources after sends a Close control frame. | 
|         /// </summary> | 
|         public void Close() | 
|         { | 
|             var data = new PayloadData(new byte[] { }); | 
|             close(data); | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Closes the connection and releases all associated resources after sends a Close control frame. | 
|         /// </summary> | 
|         /// <param name="code"> | 
|         /// A <see cref="WebSocketSharp.Frame.CloseStatusCode"/> that contains a status code indicating a reason for closure. | 
|         /// </param> | 
|         public void Close(CloseStatusCode code) | 
|         { | 
|             Close(code, String.Empty); | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Closes the connection and releases all associated resources after sends a Close control frame. | 
|         /// </summary> | 
|         /// <param name="code"> | 
|         /// A <see cref="ushort"/> that contains a status code indicating a reason for closure. | 
|         /// </param> | 
|         public void Close(ushort code) | 
|         { | 
|             Close(code, String.Empty); | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Closes the connection and releases all associated resources after sends a Close control frame. | 
|         /// </summary> | 
|         /// <param name="code"> | 
|         /// A <see cref="WebSocketSharp.Frame.CloseStatusCode"/> that contains a status code indicating a reason for closure. | 
|         /// </param> | 
|         /// <param name="reason"> | 
|         /// A <see cref="string"/> that contains a reason for closure. | 
|         /// </param> | 
|         public void Close(CloseStatusCode code, string reason) | 
|         { | 
|             Close((ushort)code, reason); | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Closes the connection and releases all associated resources after sends a Close control frame. | 
|         /// </summary> | 
|         /// <param name="code"> | 
|         /// A <see cref="ushort"/> that contains a status code indicating a reason for closure. | 
|         /// </param> | 
|         /// <param name="reason"> | 
|         /// A <see cref="string"/> that contains a reason for closure. | 
|         /// </param> | 
|         public void Close(ushort code, string reason) | 
|         { | 
|             string msg; | 
|             if (!isValidCloseStatusCode(code, out msg)) | 
|             { | 
|                 onError(msg); | 
|                 return; | 
|             } | 
|   | 
|             close(code, reason); | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Establishes a connection. | 
|         /// </summary> | 
|         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."); | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Closes the connection and releases all associated resources after sends a Close control frame. | 
|         /// </summary> | 
|         /// <remarks> | 
|         /// Call <see cref="Dispose"/> when you are finished using the <see cref="WebSocketSharp.WebSocket"/>. The | 
|         /// <see cref="Dispose"/> method leaves the <see cref="WebSocketSharp.WebSocket"/> in an unusable state. After | 
|         /// calling <see cref="Dispose"/>, you must release all references to the <see cref="WebSocketSharp.WebSocket"/> so | 
|         /// the garbage collector can reclaim the memory that the <see cref="WebSocketSharp.WebSocket"/> was occupying. | 
|         /// </remarks> | 
|         public void Dispose() | 
|         { | 
|             Close(CloseStatusCode.AWAY); | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Sends a Ping frame using the connection. | 
|         /// </summary> | 
|         /// <returns> | 
|         /// <c>true</c> if the WebSocket receives a Pong frame in a time; otherwise, <c>false</c>. | 
|         /// </returns> | 
|         public bool Ping() | 
|         { | 
|             return Ping(String.Empty); | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Sends a Ping frame with a message using the connection. | 
|         /// </summary> | 
|         /// <param name="message"> | 
|         /// A <see cref="string"/> that contains the message to be sent. | 
|         /// </param> | 
|         /// <returns> | 
|         /// <c>true</c> if the WebSocket receives a Pong frame in a time; otherwise, <c>false</c>. | 
|         /// </returns> | 
|         public bool Ping(string message) | 
|         { | 
|             if (message == null) | 
|                 message = String.Empty; | 
|   | 
|             return _isClient | 
|                    ? ping(message, 5 * 1000) | 
|                    : ping(message, 1 * 1000); | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Sends a text data using the connection. | 
|         /// </summary> | 
|         /// <param name="data"> | 
|         /// A <see cref="string"/> that contains the text data to be sent. | 
|         /// </param> | 
|         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); | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Sends a binary data using the connection. | 
|         /// </summary> | 
|         /// <param name="data"> | 
|         /// An array of <see cref="byte"/> that contains the binary data to be sent. | 
|         /// </param> | 
|         public void Send(byte[] data) | 
|         { | 
|             if (data == null) | 
|             { | 
|                 onError("'data' must not be null."); | 
|                 return; | 
|             } | 
|   | 
|             send(Opcode.BINARY, data); | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Sends a binary data using the connection. | 
|         /// </summary> | 
|         /// <param name="file"> | 
|         /// A <see cref="FileInfo"/> that contains the binary data to be sent. | 
|         /// </param> | 
|         public void Send(FileInfo file) | 
|         { | 
|             if (file == null) | 
|             { | 
|                 onError("'file' must not be null."); | 
|                 return; | 
|             } | 
|   | 
|             using (FileStream fs = file.OpenRead()) | 
|             { | 
|                 send(Opcode.BINARY, fs); | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Sends a text data asynchronously using the connection. | 
|         /// </summary> | 
|         /// <param name="data"> | 
|         /// A <see cref="string"/> that contains the text data to be sent. | 
|         /// </param> | 
|         /// <param name="completed"> | 
|         /// An <see cref="Action"/> delegate that contains the method(s) that is called when an asynchronous operation completes. | 
|         /// </param> | 
|         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); | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Sends a binary data asynchronously using the connection. | 
|         /// </summary> | 
|         /// <param name="data"> | 
|         /// An array of <see cref="byte"/> that contains the binary data to be sent. | 
|         /// </param> | 
|         /// <param name="completed"> | 
|         /// An <see cref="Action"/> delegate that contains the method(s) that is called when an asynchronous operation completes. | 
|         /// </param> | 
|         public void SendAsync(byte[] data, Action completed) | 
|         { | 
|             if (data == null) | 
|             { | 
|                 onError("'data' must not be null."); | 
|                 return; | 
|             } | 
|   | 
|             sendAsync(Opcode.BINARY, data, completed); | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Sends a binary data asynchronously using the connection. | 
|         /// </summary> | 
|         /// <param name="file"> | 
|         /// A <see cref="FileInfo"/> that contains the binary data to be sent. | 
|         /// </param> | 
|         /// <param name="completed"> | 
|         /// An <see cref="Action"/> delegate that contains the method(s) that is called when an asynchronous operation completes. | 
|         /// </param> | 
|         public void SendAsync(FileInfo file, Action completed) | 
|         { | 
|             if (file == null) | 
|             { | 
|                 onError("'file' must not be null."); | 
|                 return; | 
|             } | 
|   | 
|             sendAsync(Opcode.BINARY, file.OpenRead(), completed); | 
|         } | 
|   | 
|         #endregion | 
|     } | 
| } |