using System; using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Coe.WebSocketWrapper { public class WebSocketWrapper { private const int ReceiveChunkSize = 1024; private const int SendChunkSize = 1024; private readonly ClientWebSocket _ws; private readonly Uri _uri; private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private readonly CancellationToken _cancellationToken; private Action _onConnected; private Action _onMessage; private Action _onDisconnected; protected WebSocketWrapper(string uri) { _ws = new ClientWebSocket(); _ws.Options.KeepAliveInterval = TimeSpan.FromSeconds(20); _uri = new Uri(uri); _cancellationToken = _cancellationTokenSource.Token; } /// /// Creates a new instance. /// /// The URI of the WebSocket server. /// public static WebSocketWrapper Create(string uri) { return new WebSocketWrapper(uri); } /// /// Connects to the WebSocket server. /// /// public WebSocketWrapper Connect() { ConnectAsync(); return this; } /// /// Set the Action to call when the connection has been established. /// /// The Action to call. /// public WebSocketWrapper OnConnect(Action onConnect) { _onConnected = onConnect; return this; } /// /// Set the Action to call when the connection has been terminated. /// /// The Action to call /// public WebSocketWrapper OnDisconnect(Action onDisconnect) { _onDisconnected = onDisconnect; return this; } /// /// Set the Action to call when a messages has been received. /// /// The Action to call. /// public WebSocketWrapper OnMessage(Action onMessage) { _onMessage = onMessage; return this; } /// /// 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 (_ws.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 _ws.SendAsync(new ArraySegment(messageBuffer, offset, count), WebSocketMessageType.Text, lastMessage, _cancellationToken); } } private async void ConnectAsync() { await _ws.ConnectAsync(_uri, _cancellationToken); CallOnConnected(); StartListen(); } private async void StartListen() { var buffer = new byte[ReceiveChunkSize]; try { while (_ws.State == WebSocketState.Open) { var stringResult = new StringBuilder(); WebSocketReceiveResult result; do { result = await _ws.ReceiveAsync(new ArraySegment(buffer), _cancellationToken); if (result.MessageType == WebSocketMessageType.Close) { await _ws.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 { _ws.Dispose(); } } private void CallOnMessage(StringBuilder stringResult) { if (_onMessage != null) RunInTask(() => _onMessage(stringResult.ToString(), this)); } 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); } } }