Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Wrapper for the .NET 4.5 ClientWebSocket
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<WebSocketWrapper> _onConnected;
private Action<string, WebSocketWrapper> _onMessage;
private Action<WebSocketWrapper> _onDisconnected;
protected WebSocketWrapper(string uri)
{
_ws = new ClientWebSocket();
_ws.Options.KeepAliveInterval = TimeSpan.FromSeconds(20);
_uri = new Uri(uri);
_cancellationToken = _cancellationTokenSource.Token;
}
/// <summary>
/// Creates a new instance.
/// </summary>
/// <param name="uri">The URI of the WebSocket server.</param>
/// <returns></returns>
public static WebSocketWrapper Create(string uri)
{
return new WebSocketWrapper(uri);
}
/// <summary>
/// Connects to the WebSocket server.
/// </summary>
/// <returns></returns>
public WebSocketWrapper Connect()
{
ConnectAsync();
return this;
}
/// <summary>
/// Set the Action to call when the connection has been established.
/// </summary>
/// <param name="onConnect">The Action to call.</param>
/// <returns></returns>
public WebSocketWrapper OnConnect(Action<WebSocketWrapper> onConnect)
{
_onConnected = onConnect;
return this;
}
/// <summary>
/// Set the Action to call when the connection has been terminated.
/// </summary>
/// <param name="onDisconnect">The Action to call</param>
/// <returns></returns>
public WebSocketWrapper OnDisconnect(Action<WebSocketWrapper> onDisconnect)
{
_onDisconnected = onDisconnect;
return this;
}
/// <summary>
/// Set the Action to call when a messages has been received.
/// </summary>
/// <param name="onMessage">The Action to call.</param>
/// <returns></returns>
public WebSocketWrapper OnMessage(Action<string, WebSocketWrapper> onMessage)
{
_onMessage = onMessage;
return this;
}
/// <summary>
/// Send a message to the WebSocket server.
/// </summary>
/// <param name="message">The message to send</param>
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<byte>(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<byte>(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);
}
}
}
@syndicatedshannon

This comment has been minimized.

Copy link

syndicatedshannon commented Jan 27, 2016

Doesn't:

        await _ws.ConnectAsync(_uri, _cancellationToken);
        CallOnConnected();

cause exceptions when the cancellation token is used?

@cunaim0

This comment has been minimized.

Copy link

cunaim0 commented Jul 15, 2017

hi,
does anyone has an idea why while using the code above on existing websocket api, i fail to connect with the error "cannot connect to remote sever", and if i use javascript (with WebSocket object), with the same uri exactly, i can establish connection ?
i checked the uri betweeen the two code. there is no difference.
thanks.

@PsiOps

This comment has been minimized.

Copy link

PsiOps commented Dec 13, 2017

I believe it's bad practice to return void in an async method. Returning a Task is considered better. Other than that, nice work!

@iamdanthedev

This comment has been minimized.

Copy link

iamdanthedev commented May 30, 2019

thanks, this is very helpful

@mikealexander

This comment has been minimized.

Copy link

mikealexander commented Jul 2, 2019

This is really useful, thanks @xamlmonkey

@igorluna

This comment has been minimized.

Copy link

igorluna commented Aug 15, 2019

This helped me a lot. I had to add some methods for byte traffic and custom headers, hope it improves

    public WebSocketWrapper(string uri, Dictionary<string, string> headers) : this(uri)
    {
        _ws = new ClientWebSocket();
        _ws.Options.KeepAliveInterval = TimeSpan.FromSeconds(20);
        foreach (var header in headers)
        {
            _ws.Options.SetRequestHeader(header.Key, header.Value);
        }
        _uri = new Uri(uri);
        _cancellationToken = _cancellationTokenSource.Token;
    }

    public void SendBytes(byte[] bytes)
    {
        SendBytesAsync(bytes);
    }

    private async void SendBytesAsync(byte[] bytes)
    {
        if (_ws.State != WebSocketState.Open)
        {
            throw new Exception("Connection is not open.");
        }

        var messageBuffer = bytes;
        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<byte>(bytes, offset, count), WebSocketMessageType.Binary, lastMessage, _cancellationToken);
        }
    }

`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.