SignalR .NET客户端不支持Windows 7上的WebSockets

问题描述:

我已经使用SignalR编写了一个小型回声服务器(.net 4.5),控制台客户端(.net 4.5)和Web客户端,并给出了示例hereSignalR .NET客户端不支持Windows 7上的WebSockets

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

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

正确:.NET客户端仅在Win8及更高版本上使用WebSocket。

+0

谢谢古斯塔沃。我在Windows 7上调试过SignalR客户端,发现ClientWebSocket ctor抛出NotSupportedException异常。根据[MSDN](http://msdn.microsoft.com/zh-cn/library/system.net.websockets.clientwebsocket.aspx)支持的平台是Windows 8,Windows Server 2012. – 2013-03-15 09:49:53

+0

Peter给出的答案可能成为解决此限制的解决方案。 – 2016-01-27 10:56:23

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

对于不支持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); 
+0

您是如何在.NET 4.0上为服务器端添加websocket支持的? – sqenixs 2016-08-10 20:11:00