【问题标题】:SignalR .NET Client doesn't support WebSockets on Windows 7SignalR .NET 客户端不支持 Windows 7 上的 WebSockets
【发布时间】:2013-03-01 11:02:21
【问题描述】:

我使用 SignalR 编写了一个小型 echo-server (.net 4.5)、控制台客户端 (.net 4.5) 和 Web 客户端,并提供了示例 here

服务器托管在 IIS8/Win8 中。然后我在 Win7 上运行了两个客户端。我看到 Chrome 中的 Web 客户端使用 webSockets,而控制台应用程序客户端使用 serverSentEvents。如果我在 Win8 上运行控制台客户端,则 webSockets 传输正在使用中。

SignalR .NET 客户端真的只能在 Win8 及更高版本上使用 webSockets 吗?

【问题讨论】:

    标签: windows-7 signalr .net-4.5 signalr.client


    【解决方案1】:

    没错:.NET 客户端仅在 Win8 及更高版本上使用 WebSockets。

    【讨论】:

    • 感谢古斯塔沃。我在 Windows 7 上调试了 SignalR 客户端,发现 ClientWebSocket ctor 抛出了 NotSupportedException。而根据MSDN 支持的平台是Windows 8、Windows Server 2012。
    • 彼得给出的答案可能是解决此限制的解决方案。
    【解决方案2】:

    对于一个项目,我必须结合 SignalR 使用真正的 websocket 连接。

    对于不支持 websockets 的 Windows 版本,您可以使用WebSocket4Net NuGet 包和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, "");
          }
        }
      }
    }
    

    要创建 websocket 客户端,请使用 try-catch 来确定应该使用哪个 websocket 实现。

    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;
      }
    }
    

    开始连接到 SignalR 集线器。

    var hubConnection = new HubConnection("https://url/to/the/hub");
    var clientTransport = WebSocketTransportFactory.Create();
    await hubConnection.Start(clientTransport);
    

    【讨论】:

    • 您是如何在 .NET 4.0 上为服务器端添加 websocket 支持的?
    • 这也适用于Microsoft.AspNetCore.SignalR.Client 吗?
    【解决方案3】:

    这是一个老问题,但如果您将项目转换为自包含的 .NET 核心应用程序,您可以在 Windows 7 上将 SignalR 与 WebSockets 一起使用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-05-24
      • 1970-01-01
      • 1970-01-01
      • 2011-06-26
      • 2020-08-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多