65

Is there a way to find out the number of listeners (clients connected to a hub?)

I'm trying to run/start a task if at least one client is connected, otherwise do not start it:

[HubName("taskActionStatus")]
public class TaskActionStatus : Hub, IDisconnect
{
    static CancellationTokenSource tokenSource;

    public void GetTasksStatus(int? siteId)
    {
        tokenSource = new CancellationTokenSource();
        CancellationToken ct = tokenSource.Token;

        ITaskRepository taskRepository = UnityContainerSetup.Container.Resolve<ITaskRepository>();

        // init task for checking task statuses
        var tasksItem = new DownloadTaskItem();
        taskRepository.GetTasksStatusAsync(siteId, tasksItem, ct);

        // subscribe to event [ listener ]
        tasksItem.Changed += new EventHandler<TaskEventArgs>(UpdateTasksStatus);
    }

    public void UpdateTasksStatus(object sender, TaskEventArgs e)
    {
        Clients.updateMessages(e.Tasks);
    }

    // when browsing away from page
    public Task Disconnect()
    {
        try
        {
            tokenSource.Cancel();
        }
        catch (Exception)
        {
            //
        }

        return null;
    }
}

thanks

Ogglas
  • 62,132
  • 37
  • 328
  • 418
ShaneKm
  • 20,823
  • 43
  • 167
  • 296
  • Take a look at https://stackoverflow.com/questions/21066657/signalr-ondisconnected-a-reliable-way-to-handle-user-is-online-for-chatroom/21070978#21070978 – KRoy Jun 14 '18 at 17:02

6 Answers6

115

There is no way to get this count from SignalR as such. You have to use the OnConnect() and OnDisconnect() methods on the Hub to keep the count yourself.

Simple example with a static class to hold the count:

public static class UserHandler
{
    public static HashSet<string> ConnectedIds = new HashSet<string>();
}

public class MyHub : Hub
{
    public override Task OnConnectedAsync()
    {
        UserHandler.ConnectedIds.Add(Context.ConnectionId);
        return base.OnConnectedAsync();
    }

    public override Task OnDisconnectedAsync(Exception exception)
    {
        UserHandler.ConnectedIds.Remove(Context.ConnectionId);
        return base.OnDisconnectedAsync(exception);
    }
}

You then get the count from UserHandler.ConnectedIds.Count.

VGs
  • 113
  • 7
Anders Arpi
  • 8,277
  • 3
  • 33
  • 49
  • do you have an example of hos this is used? would I keep this list in a session? – ShaneKm Nov 22 '12 at 14:03
  • It's usually best to keep the count on the server in some type of data store (possibly just a static class). I can outline a solution in my answer, give me a minute... – Anders Arpi Nov 22 '12 at 14:05
  • 7
    +1 just remember on app domain restart that object will get reset; you should consider persisting that object at some point; I usually do it everytime someone gets added or removed. – cillierscharl Nov 22 '12 at 14:25
  • that's weird, i get: signalR OnConnected()': no suitable method found to override. ?? – ShaneKm Nov 23 '12 at 06:13
  • Ah, you might be using an older version of SignalR then (they are currently going through a lot of changes for the 1.0.0 release which is in alpha at the moment). In that case implement the IConnect and IDisconnect interfaces and their corresponding methods. – Anders Arpi Nov 23 '12 at 08:21
  • 19
    Using a static class like this works fine if you are only ever going to be running one server, but if you are in the cloud and are using a load balancer between multiple servers this solution falls apart. You could use a cache server to store the connected users however. We use Redis hosted in Azure. – Anj Nov 21 '14 at 16:17
  • @Anj Definitely, using a static class for storing any data like this (even if it's "only" cached data) usually is a bad idea due to the scenario you mentioned. For – Anders Arpi Jan 07 '15 at 09:01
  • @Anj What happens, if the server crashes? Will the counter in the database be adjusted? OK, the clients might try to reconnect, but if e. g. all servers fail you have to detect it and reset the counter to 0. Another problem occurs, if a server crashes and at the same time a client disappears. Will the remaining servers detect it? Any idea, how to solve such problems? – Niklas Peter Oct 06 '15 at 16:18
  • @NiklasPeter Which server? The cache server or the app server? You would want to have startup code to ensure the cache is initialized with the appropriate startup state. Not to mention, if all your servers fail, the connected count of signalR clients is far from your biggest worry. – Anj Oct 07 '15 at 17:12
  • 1
    @Anj The server running SignalR itself, the app server. It is not your biggest worry, but it is a worry. You could save more information about the listeners than just a counter and implement an application-level heartbeat to remove orphaned clients. However actually such information must exist inside SignalR, but (as MS says) you can not query it by design . – Niklas Peter Oct 07 '15 at 20:00
  • 8
    Don't forget to override OnReconnected in SignalR 2.2.1. If client reconnect with the same connection_id, OnConnect won't be called. – Yan Oreshchenkov Mar 20 '17 at 06:05
  • It lacks a bool parameter in order to overide the OnDisconnected method, I encountered the compiler error: **No suitable method found to override**, it just need to change like this: `public override Task OnDisconnected(bool stopCalled)` as sommebody else mentioned in another answer right below this on. – Muhammad Musavi Jan 17 '18 at 05:04
  • 7
    `static HashSet ConnectedIds` doesn't seem thread-safe at all. – Pang Nov 13 '20 at 04:13
  • Yea, monitored HashSet with SemaphoreSlim lock mechanism should be used. – Vinigas Jan 19 '21 at 16:15
  • @Vinigas Or simply use a `ConcurrentDictionary` with keys and an ignored value. – Blue May 21 '22 at 05:39
29

For version 2.1.1< it should be:

public static class UserHandler
{
    public static HashSet<string> ConnectedIds = new HashSet<string>();
}

public class MyHub : Hub
{
    public override Task OnConnected()
    {
        UserHandler.ConnectedIds.Add(Context.ConnectionId);
        return base.OnConnected();
    }

    public override Task OnDisconnected(bool stopCalled)
    {
        UserHandler.ConnectedIds.Remove(Context.ConnectionId);
        return base.OnDisconnected(stopCalled);
    }
}
Ogglas
  • 62,132
  • 37
  • 328
  • 418
  • 3
    Be careful with it in case of server sharding. Prefer to use Cache or Db to store your canters. – Nigrimmist Oct 14 '15 at 13:50
  • Keep in mind that if your server application is running in Azure and it scales vertically, this will not work. You need a shared cache like Redis or a SQL/NoSQL database (or any other shared resource) to track connections. – Andy Apr 28 '18 at 03:35
  • 1
    now you need OnConnectedAsync and OnDisconnectedAsync – kofifus Jun 15 '18 at 06:51
  • 3
    as far as I know, HasSet is not thread safe, thus I think we need some synchronization in this approach, while accessing ConnectedIds – teroplut Mar 29 '19 at 09:25
10

In SIgnalR(version 2.4.1) it is rather easy:

public int GetOnline()
{
   return GlobalHost.DependencyResolver.Resolve<ITransportHeartbeat>().GetConnections().Count;
}

Just Invoke this method from client (:

6

Now you need:

public override Task OnConnectedAsync()
{
    UserHandler.ConnectedIds.Add(Context.ConnectionId);

    return base.OnConnectedAsync();
}

public override Task OnDisconnectedAsync(Exception exception)
{
    UserHandler.ConnectedIds.Remove(Context.ConnectionId);
    return base.OnDisconnectedAsync(exception);
}
DanB
  • 2,022
  • 1
  • 12
  • 24
ejdrian313
  • 666
  • 7
  • 13
  • 2
    IMHO, maybe you should have edited the accepted answer instead of adding a new one? Keeps the thread cleaner to have fewer answers. Also, folks are much more likely to grab the first (and, in this case, accepted) answer. – LuvForAirplanes Mar 03 '20 at 19:44
1

I would like to add that if you are using a serverless solution with Azure Functions and Azure SignalR Service there is the following resolved issue: https://github.com/Azure/azure-functions-signalrservice-extension/issues/54

It refers to this sample where you can use Event Grids to get the real-time count of connections, pretty sweet. https://github.com/aspnet/AzureSignalR-samples/tree/master/samples/EventGridIntegration

gerb0n
  • 380
  • 1
  • 5
  • 19
0

In my project which use Microsoft.AspNetCore.SignalR.Core version 1.1.0

I can debug and see the count with

((Microsoft.AspNetCore.SignalR.DefaultHubLifetimeManager<XXX.Push.SignalR.EventHub>)((Microsoft.AspNetCore.SignalR.Internal.AllClientProxy<XXX.Push.SignalR.EventHub>)((Microsoft.AspNetCore.SignalR.TypedClientBuilder.IEventImpl)((Microsoft.AspNetCore.SignalR.Internal.HubClients<XXX.Push.SignalR.EventHub, XXX.Push.SignalR.IEvent>)_hub.Clients).All)._proxy)._lifetimeManager)._connections.Count

It looks like this:

debug

我零0七
  • 315
  • 3
  • 6