SignalR and Browser Connection limit

14,452

Solution 1

This problem would be best addressed by the future Channel Messaging specification, which has not been implemented by any browsers to-date, but I managed to solve it by limiting the number of connections as described by Alex Ford and using localStorage as a message bus between tabs.

The storage event lets you propagate data between tabs while keeping a single SignalR connection open (thereby preventing connection saturation). Calling localStorage.setItem('sharedKey', sharedData) will raise the storage event in all other tabs (not the caller):

$(window).bind('storage', function (e) {
    var sharedData = localStorage.getItem('sharedKey');
    if (sharedData !== null)
        console.log(
            'A tab called localStorage.setItem("sharedData",'+sharedData+')'
        );
});

You can test if ($.connection.hub.state === 1) to determine if a given tab should notify the other tabs via localStorage (courtesy of Alex) to prevent duplicate localStorage.setItem calls.

Facebook overcomes this browser limitation by serving persistent connections over several sub-domains, but this can complicate deployment and testing.

Caveats

Old Connections: In Alex's solution, you need to be careful of Disconnect() not being called (e.g. exception), and you filling up your HubConnections bucket (or repository) with old hub connections. If the Session ID does not change (can happen), this may prevent new clients from establishing a SignalR connection even though none are active. Alternatively, timestamp new connections and have a sliding expiration to minimise potential impact.

Locking: localStorage may be subject to race conditions as it does not implement any locking as described here.

To support different types of events, you should encode an eventType in your JSON messages and test for it on the storage event.

Fallbacks

If a SignalR connection cannot be established, I fall back onto polling the server every 45 seconds to retrieve a notification count.

If you don't want to use localStorage, you can use cookies, but it's not as clean.

Solution 2

I've created IWC-SignalR utility which allows to have single SignalR connection for all windows (tabs) of the same application.

How it works

One of the windows becomes a connection owner (choosen randomly) and holds the real SignalR connection. If connection owner is closed or crashed another window becomes a connection owner - this happens automatically. Inter-window communication is done by means of inter-window communication library (based on localStorage). This library provides functionality to communicate between windows as between parallel processes (locks, shared data, event bus...). Hope it will be useful for someone.

Solution 3

To expand on @FreshCode's answer, this is how I implemented his idea.

I had a need to pass two different actions between tabs. I can either set notifications or remove notifications. These notifications are received by the browser and stored as timeouts that will fire at a specified time. The first tab has the SignalR connection and all the others do not. One issue I had to overcome was that the "storage" event would fire no matter which action I was intending to perform (set/remove).

What I ended up doing was passing along a custom JSON object with a property containing the action I wanted to perform:

$(window).bind('storage', function () {
    var updateInfo = JSON.parse(localStorage.getItem('updateInfo'));
    if (updateInfo.action == 'removeNotification')
        removeNotification(updateInfo.notificationId);
    else if (updateInfo.action == 'setNotification')
        setNotification(updateInfo.notification);
});

This way each time I set an item in local storage, I just specify the action that needs to occur and only that action will happen on the other tabs. For example, if I update a notification it is a combination of both actions when it is received by the client. It removes the notification and sets the notification with the updated values. So two calls to localStorage.setItem are being made.

function removeNotification(id) {
    // Check if signalR is connected. If so, I am the tab that will update
    // the other tabs.
    if ($.connection.hub.state === 1) {
        var updateInfo = {
            action: 'removeNotification',
            notificationId: id
        };
        localStorage.setItem('updateInfo', JSON.stringify(updateInfo));
    }
    // brevity brevity
}

Likewise, the setNotification function.

function setNotification(notification) {
    if ($.connection.hub.state === 1) {
        var updateInfo = {
            action: 'setNotification',
            notification: notification
        };
        localStorage.setItem('updateInfo', JSON.stringify(updateInfo));
    }
    // brevity brevity
}

Solution 4

Make sure SignalR can use and is using WebSockets and it won't use one of the limited number of connections your application is allowed to use.

SignalR kan use different transport protocols and how SignalR decides which to use is described here:

HTML 5 transports

These transports depend on support for HTML 5. If the client browser does not support the HTML 5 standard, older transports will be used.

  • WebSocket (if both the server and browser indicate they can support Websocket). WebSocket is the only transport that establishes a true persistent, two-way connection between client and server. However, WebSocket also has the most stringent requirements; it is fully supported only in the latest versions of Microsoft Internet Explorer, Google Chrome, and Mozilla Firefox, and only has a partial implementation in other browsers such as Opera and Safari.
  • Server Sent Events, also known as EventSource (if the browser supports Server Sent Events, which is basically all browsers except Internet Explorer.)

and

Transport selection process The following list shows the steps that SignalR uses to decide which transport to use.

  1. If the browser is Internet Explorer 8 or earlier, Long Polling is used.

  2. If JSONP is configured (that is, the jsonp parameter is set to true when the connection is started), Long Polling is used.

  3. If a cross-domain connection is being made (that is, if the SignalR endpoint is not in the same domain as the hosting page), then WebSocket will be used if the following criteria are met:

    • The client supports CORS (Cross-Origin Resource Sharing). For details on which clients support CORS, see CORS at caniuse.com.

    • The client supports WebSocket

    • The server supports WebSocket

      If any of these criteria are not met, Long Polling will be used. For more information on cross-domain connections, see How to establish a cross-domain connection.

  4. If JSONP is not configured and the connection is not cross-domain, WebSocket will be used if both the client and server support it.

  5. If either the client or server do not support WebSocket, Server Sent Events is used if it is available.

  6. If Server Sent Events is not available, Forever Frame is attempted.

  7. If Forever Frame fails, Long Polling is used.

Using developer tools, check the network calls made on your page. You should be able to see something like:

.../connect?transport=webSockets&clientProtocol=1.5&connectionToken=...

If transport is something else than webSockets (for example serverSentEvents or longPolling) you might want to troubleshoot client and server to see why the handshake does not result in using WebSockets. (In my case, I had missed installing the WebSocket protocol windows feature for IIS.

Share:
14,452

Related videos on Youtube

Liron Harel
Author by

Liron Harel

Updated on June 06, 2022

Comments

  • Liron Harel
    Liron Harel about 2 years

    I made a simple app with SignalR for testing. When the page loads it calls a function on the server, that function then calls a client function that prints a message on the screen. I did that to check that both the client and server function works and SignalR communication works ok.

    My problem is that if I open the same page on two different tabs (did it in Chrome), the first page loads ok, but the second page doesn't call the server's functions - ONLY if I close the first page.

    So as far as I understand, their is probably a connection limitation that is related to the browser that doesn't allow SignalR to connect more then once (actually two, one for receiving and one for sending)

    Update: I've find our that other tabs where open, but now I've checked it through and it allows only 4 tabs / pages to be active with connections. If I try to put the same page on a new tab no data is being sent, when I close one of the other tabs, the new tab sends the data right away.

    What I wanted to know if there is any solution for that, because I want this connectivity to be available if the user decide to open the same page on two tabs or more.

    I don't believe that it has anything to do with IIS, because from what I know it can accept thousands of connections.

    • N. Taylor Mullen
      N. Taylor Mullen over 11 years
      Are you using IIS or IIS express?
    • Liron Harel
      Liron Harel over 11 years
      testing it locally via visual studio
  • Alexander Köplinger
    Alexander Köplinger over 11 years
    This is an interesting solution, thanks for sharing! I wonder whether SignalR could abstract this too in the future, so that it only ever needs a single concurrent connection no matter how many tabs you've opened?
  • davidfowl
    davidfowl over 11 years
    Awesome solution, not sure about building this into signalr though, seems complicated. Also IIS express doesn't have an connection limit.
  • Chev
    Chev over 11 years
    I began to implement this localStorage behavior and then realized I still needed a way to limit the connections to one. I knew how I was doing it using the database but I came back to this answer to see how it was being done here. It wasn't until that point that I realized he linked to my own blog post. Haha
  • Petrus Theron
    Petrus Theron over 11 years
    @dfowler, you are correct. I have removed the statement about IIS Express having a connection limit. I must have been hitting Chrome's simultaneous connection limit (8 or 10?).
  • Petrus Theron
    Petrus Theron over 11 years
    Updated my answer with your $.connection.hub.state === 1 test and some caveats.
  • Petrus Theron
    Petrus Theron over 11 years
    @AlexFord yeah, I don't like using an SQL database for storing hub connections, due to the transitory nature of the data, but you need a centralised repository (like Redis) if you are on a web farm.
  • Chev
    Chev over 11 years
    @FreshCode I was thinking last night, I wonder if you have access to the global cache collection from signalr. All those HubConnection objects could be added to a list and serialized into some sort of global cache rather than the database. The HubConnection objects are the perfect kind of thing for storing in memory since if memory gets wiped then all the signalR connections reconnect anyway.
  • Chev
    Chev over 11 years
    I was actually wondering how to solve the issue of disconnect not firing. I figured some sort of timestamp solution would work but I couldn't work out in my head exactly how that should be implemented. For instance: If tab one connects and, for the sake of argument, stays connected for 3 days with the same sessionId. On day 3 if a second tab is opened, the HubConnection for the original connection would have a timestamp that is 3 days old but still valid.
  • Petrus Theron
    Petrus Theron over 11 years
    @AlexFord, exactly. You should have access to System.Runtime.Cache?
  • Liron Harel
    Liron Harel over 11 years
    Can you provide a sample project so I can test it out. please. HAving tough time implementing this
  • Chev
    Chev over 11 years
  • Cristian Diaconescu
    Cristian Diaconescu over 10 years
    @AlexFord The link to your blog is broken. Also, the 'let me know' link on your blog post asking people to report broken links... is broken :)
  • Chev
    Chev over 10 years
    @CristiDiaconescu I fixed the link in this answer but I'm not quite sure where you're seeing a "let me know" link. I don't even remember making such a link lol
  • Chev
    Chev over 10 years
    Fixed. Looks like my blog platform doesn't support mailto links yet. I'll have to send in a ticket.
  • G.S Bhangal
    G.S Bhangal almost 10 years
    AS i have a chat application on master page (common for all pages) and If i have to do the chat on other tabs with connection limit? can you please provide me any idea
  • G.S Bhangal
    G.S Bhangal almost 10 years
    @dfowler I am using SignalR 1 on my master page for the chat facebook chat. when i try to open more than 6 tabs in a browser (from same domain) consecutive tabs are not opened they are loading and a message waiting for available socket...shown at bottom. when i close the previos tabs then new tabs are loaded successfully I need to open all tabs (as many user want) I don't want to limit the number of connection because on every page i have to show the chat box and send the chat messages. Can you please tell me how is this possible. becasue i cannot close or limit the number of connections
  • xec
    xec over 9 years
    @AlexFord The link to the annotated javascript file appears to be dead :( I realize this is an old post, but I am struggling with the same issue and any additional info would be highly appreciated! What happens if you close the first tab with the active connection?
  • Chev
    Chev over 9 years
    @xec I no longer work at the company I did when I put that link up so I don't have access to the annotated file anymore. If I get some time I'll grok the file and re-annotate it. For now though, here's a link to the only copy I have of the working script that effectively multiplexes a single websocket connection across multiple tabs. It's been several years so it might be gross JavaScript. We'll just say I've grown since then ;)
  • William Pereira
    William Pereira over 3 years
    This answer helped me a lot. Thanks