SignalR .NET Client doesn't support WebSockets on Windows 7
Asked Answered
B

3

7

I've written a small echo-server (.net 4.5), console client (.net 4.5) and web client using SignalR and example presented here.

Server is hosted in IIS8/Win8. Then I ran both clients on Win7. and I see that web client in Chrome uses webSockets, while console app client uses serverSentEvents. If I run console client on Win8, then webSockets transport is in use.

Is it true that SignalR .NET client will use webSockets only on Win8 and higher?

Beckman answered 13/3, 2013 at 13:20 Comment(0)
B
11

It's correct: .NET client uses WebSockets only on Win8 and higher.

Baucis answered 14/3, 2013 at 17:30 Comment(2)
Thanks Gustavo. I've debugged SignalR client on Windows 7 and see that ClientWebSocket ctor throws an NotSupportedException. And according to MSDN supported platforms are Windows 8, Windows Server 2012.Beckman
The answer that Peter gave could be a solution to work around this limitation.Heft
L
5

For a project I had to use real websocket connections in combination with SignalR.

For Windows versions that don't support websockets you can use the WebSocket4Net NuGet package and the following implementation of SignalR IClientTransport.

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Client;
using Microsoft.AspNet.SignalR.Client.Http;
using Microsoft.AspNet.SignalR.Client.Infrastructure;
using Microsoft.AspNet.SignalR.Client.Transports;
using SuperSocket.ClientEngine;
using WebSocket4Net;

public sealed class WebSocket4NetTransport : ClientTransportBase
{
  private IConnection _connection;
  private string _connectionData;
  private CancellationToken _disconnectToken;
  private CancellationTokenSource _webSocketTokenSource;
  private WebSocket _webSocket4Net;
  private int _disposed;

  public TimeSpan ReconnectDelay { get; set; }

  public WebSocket4NetTransport()
    : this(new DefaultHttpClient())
  {
  }

  public WebSocket4NetTransport(IHttpClient client)
    : base(client, "webSockets")
  {
    _disconnectToken = CancellationToken.None;
    ReconnectDelay = TimeSpan.FromSeconds(2.0);
  }

  ~WebSocket4NetTransport()
  {
    Dispose(false);
  }

  protected override void OnStart(IConnection connection, string connectionData, CancellationToken disconnectToken)
  {
    _connection = connection;
    _connectionData = connectionData;
    _disconnectToken = disconnectToken;

    var connectUrl = UrlBuilder.BuildConnect(connection, Name, connectionData);

    try
    {
      PerformConnect(connectUrl);
    }
    catch(Exception ex)
    {
      TransportFailed(ex);
    }
  }

  protected override void OnStartFailed()
  {
    Dispose();
  }

  public override Task Send(IConnection connection, string data, string connectionData)
  {
    if(_webSocket4Net.State == WebSocketState.Open)
    {
      _webSocket4Net.Send(data);
    }

    var ex = new InvalidOperationException("Socket closed");
    connection.OnError(ex);

    throw ex;
  }

  public override void LostConnection(IConnection connection)
  {
    _connection.Trace(TraceLevels.Events, "WS: LostConnection");

    if(_webSocketTokenSource == null)
    {
      return;
    }

    _webSocketTokenSource.Cancel();
  }

  public override bool SupportsKeepAlive
  {
    get { return true; }
  }

  protected override void Dispose(bool disposing)
  {
    if(disposing)
    {
      if(Interlocked.Exchange(ref _disposed, 1) == 1)
      {
        base.Dispose(true);
        return;
      }

      if(_webSocketTokenSource != null)
      {
        _webSocketTokenSource.Cancel();
      }

      if(_webSocket4Net != null)
      {
        DisposeWebSocket4Net();
      }

      if(_webSocketTokenSource != null)
      {
        _webSocketTokenSource.Dispose();
      }
    }

    base.Dispose(disposing);
  }

  private void DisposeWebSocket4Net()
  {
    _webSocket4Net.Error -= WebSocketOnError;
    _webSocket4Net.Opened -= WebSocketOnOpened;
    _webSocket4Net.Closed -= WebSocketOnClosed;
    _webSocket4Net.MessageReceived -= WebSocketOnMessageReceived;

    _webSocket4Net.Dispose();
    _webSocket4Net = null;
  }

  private void PerformConnect(string url)
  {
    if(_webSocket4Net != null)
    {
      DisposeWebSocket4Net();
    }

    _webSocketTokenSource = new CancellationTokenSource();
    _webSocketTokenSource.Token.Register(WebSocketTokenSourceCanceled);
    CancellationTokenSource.CreateLinkedTokenSource(_webSocketTokenSource.Token, _disconnectToken);

    // Add the header from the connection to the socket connection
    var headers = _connection.Headers.ToList();

    // SignalR uses https, websocket4net uses wss
    url = url.Replace("http://", "ws://").Replace("https://", "wss://");

    _webSocket4Net = new WebSocket(url, customHeaderItems: headers);

    _webSocket4Net.Error += WebSocketOnError;
    _webSocket4Net.Opened += WebSocketOnOpened;
    _webSocket4Net.Closed += WebSocketOnClosed;
    _webSocket4Net.MessageReceived += WebSocketOnMessageReceived;

    _webSocket4Net.Open();
  }

  private async Task DoReconnect()
  {
    string reconnectUrl = UrlBuilder.BuildReconnect(_connection, Name, _connectionData);

    while(TransportHelper.VerifyLastActive(_connection))
    {
      if(_connection.EnsureReconnecting())
      {
        try
        {
          PerformConnect(reconnectUrl);
          break;
        }
        catch(OperationCanceledException)
        {
          break;
        }
        catch(Exception ex)
        {
          _connection.OnError(ex);
        }
        await Task.Delay(ReconnectDelay, CancellationToken.None);
      }
      else
      {
        break;
      }
    }
  }

  private void WebSocketOnOpened(object sender, EventArgs e)
  {
    _connection.Trace(TraceLevels.Events, "WS: OnOpen()");

    if(!_connection.ChangeState(ConnectionState.Reconnecting, ConnectionState.Connected))
    {
      return;
    }

    _connection.OnReconnected();
  }

  private async void WebSocketOnClosed(object sender, EventArgs e)
  {
    _connection.Trace(TraceLevels.Events, "WS: OnClose()");

    if(_disconnectToken.IsCancellationRequested || AbortHandler.TryCompleteAbort())
    {
      return;
    }

    await DoReconnect();
  }

  private void WebSocketOnError(object sender, ErrorEventArgs e)
  {
    var exception = e.Exception;
    _connection.OnError(exception);
  }

  private void WebSocketOnMessageReceived(object sender, MessageReceivedEventArgs e)
  {
    var message = e.Message;

    _connection.Trace(TraceLevels.Messages, "WS: OnMessage({0})", (object)message);
    ProcessResponse(_connection, message);
  }

  private void WebSocketTokenSourceCanceled()
  {
    if(_webSocketTokenSource.IsCancellationRequested)
    {
      if(_webSocket4Net.State != WebSocketState.Closed)
      {
        _webSocket4Net.Close(1000, "");
      }
    }
  }
}

To create a websocket client use a try-catch to detemine which websocket implementation should be used.

using System;
using System.Net.WebSockets;
using Microsoft.AspNet.SignalR.Client.Transports;

public static class WebSocketTransportFactory
{
  public static IClientTransport Create()
  {
    IClientTransport clientTransport;

    try
    {
      // Test if .net websockets are supported
      // Supported since Windows 8 and newer
      var testSocket = new ClientWebSocket();

      clientTransport = new WebSocketTransport();
    }
    catch(PlatformNotSupportedException)
    {
      clientTransport = new WebSocket4NetTransport();
    }

    return clientTransport;
  }
}

Start connecting to the SignalR hub.

var hubConnection = new HubConnection("https://url/to/the/hub");
var clientTransport = WebSocketTransportFactory.Create();
await hubConnection.Start(clientTransport);
Lebkuchen answered 3/1, 2016 at 21:38 Comment(2)
How did you go about adding websocket support for the server side on .NET 4.0?Acosmism
Could this apply to Microsoft.AspNetCore.SignalR.Client also?Trochanter
E
0

This is an old question, but if you convert your project to be a self contained .NET core app you can use SignalR with WebSockets on Windows 7.

Enclose answered 23/8, 2019 at 18:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.