using System; using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace System.Net.Sockets { public class WebSocketWrapper : IDisposable { private const int ReceiveChunkSize = 1024; private const int SendChunkSize = 1024; public readonly ClientWebSocket Client; private readonly Uri _uri; private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private readonly CancellationToken _cancellationToken; public event Action OnConnected; public event Action OnMessage; public event Action OnDisconnected; protected WebSocketWrapper(Uri uri) { Client = new ClientWebSocket(); Client.Options.KeepAliveInterval = TimeSpan.FromSeconds(20); _uri = uri; _cancellationToken = _cancellationTokenSource.Token; } /// /// Creates a new instance. /// /// The URI of the WebSocket server. /// public static WebSocketWrapper Create(Uri uri) { return new WebSocketWrapper(uri); } /// /// Connects to the WebSocket server. /// /// public WebSocketWrapper Connect() { ConnectAsync(); return this; } /// /// Connects to the WebSocket server. /// /// public void Disconnect() { Client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Normal", CancellationToken.None); } /// /// Send a message to the WebSocket server. /// /// The message to send public void SendMessage(string message) { SendMessageAsync(message); } private async void SendMessageAsync(string message) { if (Client.State != WebSocketState.Open) { throw new Exception("Connection is not open."); } var messageBuffer = Encoding.UTF8.GetBytes(message); var messagesCount = (int)Math.Ceiling((double)messageBuffer.Length / SendChunkSize); for (var i = 0;i < messagesCount;i++) { var offset = (SendChunkSize * i); var count = SendChunkSize; var lastMessage = ((i + 1) == messagesCount); if ((count * (i + 1)) > messageBuffer.Length) { count = messageBuffer.Length - offset; } await Client.SendAsync(new ArraySegment(messageBuffer, offset, count), WebSocketMessageType.Text, lastMessage, _cancellationToken); } } private async void ConnectAsync() { await Client.ConnectAsync(_uri, _cancellationToken); CallOnConnected(); StartListen(); } private async void StartListen() { var buffer = new byte[ReceiveChunkSize]; try { while (Client.State == WebSocketState.Open) { var stringResult = new StringBuilder(); WebSocketReceiveResult result; do { result = await Client.ReceiveAsync(new ArraySegment(buffer), _cancellationToken); if (result.MessageType == WebSocketMessageType.Close) { await Client.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); CallOnDisconnected(); } else { var str = Encoding.UTF8.GetString(buffer, 0, result.Count); stringResult.Append(str); } } while (!result.EndOfMessage); CallOnMessage(stringResult); } } catch (Exception) { CallOnDisconnected(); } finally { Client.Dispose(); } } private void CallOnMessage(StringBuilder stringResult) { if (OnMessage != null) RunInTask(() => OnMessage(this, stringResult.ToString())); } private void CallOnDisconnected() { if (OnDisconnected != null) RunInTask(() => OnDisconnected(this)); } private void CallOnConnected() { if (OnConnected != null) RunInTask(() => OnConnected(this)); } private static void RunInTask(Action action) { Task.Factory.StartNew(action); } public void Dispose() { if (Client != null) { Client.Dispose(); } } } }