4

While developing a server client push mechanism using SignalR I have run into the maximum concurrent connections problem. I.e. each browser process only supports around 6 concurrent connections to the same domain.

I am currently keeping track of which users are associated with which connections and after a user has three open connections I would like to force additional connections from the same user to use long polling for a transport as opposed to web sockets. In this way I hope to avoid the problem of running out of connections entirely.

I could always do this on the client with the server providing a non SignalR mechanism to inform the client if they have run out of web socket connections and the client could then specify long polling when it opens the SignalR connection but this seems inefficient. I would really like to set the transport as appropriate when the connection opens.

Something like the following in the hub class.

    /// <summary>
    /// Chooses the correct transport depending on a users connections
    /// </summary>        
    public override Task OnConnected()
    {
        if(CanOpenWebSocketsConnection(Context.User.Identity.Name))
        {
            Connection.Transport = Connections.WebSockets;
        }
        else
        {
            Connection.Transport = Connections.LongPolling;
        }
        return base.OnConnected();                
    }
AncientSyntax
  • 919
  • 8
  • 12
  • 1
    Why not just enforce that there is only one connection total per client? Your server will be much more scalable in the long run. – Jesse Carter Dec 18 '14 at 14:20
  • Define client, is a client an IP address, a user? If we take that approach then we won't be able to leverage the webclient transport to provide dynamic updates via SignalR without preventing the user from opening another tab to any page on the site. Using the approach described above we would get the best of both worlds. Dynamic updates with a fallback to a slightly less frequent update and the user can open as many tabs as they like. It is worth mentioning this won't have to scale beyond a few hundred users. – AncientSyntax Dec 18 '14 at 14:26
  • By your own admission you're allowing multiple connections per *user*. I apologize for using the word client in my original comment. Having multiple connections to the same user is wasting server resources. All communication could be going through the same line. – Jesse Carter Dec 18 '14 at 14:28
  • It could, see [Channel Messaging](https://html.spec.whatwg.org/multipage/comms.html#channel-messaging) and this [response](http://stackoverflow.com/a/13036955/3760643). However we are quite happy with the approach described. – AncientSyntax Dec 18 '14 at 14:36

1 Answers1

9

As traditional longPolling and iFrame mechanisms SignalR transport type is initiated by Client, not Server. SignalR is simply a wrapper of these transport types. A workaround for this question is to have the Server to tell the Client to reconnect using a specific type.

Server code:

[HubName("moveShape")]
public class MoveShapeHub : Hub
{
    public override Task OnConnected()
    {
        if (Context.QueryString["transport"] == "webSockets")
        {
            return Clients.Caller.changeTransport("longPolling");
        }
    }
}

Client code:

var hubConnection = new HubConnection("http://localhost:1235/");
var hub = hubConnection.CreateHubProxy("moveShape");

hub.On<string>("changeTransport", transportName =>
    Dispatcher.InvokeAsync(() =>
    {
        if (transportName == "longPolling")
        {
            hubConnection.Stop();
            hubConnection.Start(new LongPollingTransport());
        }
    }));

await hubConnection.Start();

I have tested this solution. It works under SignalR v2.2. Here is my Github sample project. (Somehow under SignalR v0.5.2, the hubConnection does not restart once stopped).

Jeson Martajaya
  • 6,996
  • 7
  • 54
  • 56