Wednesday, May 13, 2015 Eric Richards

Returning Data from the Client to the Hub with SignalR

If you have not used it yet, Signalr is a real-time web communication library which is now under the ASP.NET banner. It is a very cool technology, which allows you to actively push data from a server application to your clients, without having to worry too much about the particular transport mechanism by which the content is delivered - SignalR ideally uses WebSockets, but degrades gracefully to older transport protocols (like AJAX long polling), if WebSocket connections are not supported by either the client or server. This is very powerful, since it allows you to write a single implementation, focused on your high-level needs, and SignalR handles most of the nitty gritty work for you. In addition, while SignalR can be used as something of a dumb pipe (although not that dumb, since you do get object->JSON serialization out of the box) via PersistentConnection, the real power of SignalR lies in its Hubs concept. By connecting to a SignalR Hub, a client can call methods on the server, both to execute commands, or to query for data. Similarly, the server can issue commands to the client by calling client-side callback methods.

This allows you to do some pretty cool things. Probably the canonical example is web-based instant-messaging/chat rooms, which is dead simple to implement using SignalR. But really any kind web page that polls for new data on an interval is a good candidate for SignalR, like, say, real-time dashboards, or a sports score ticker, maybe some classes of multiplayer games.

One scenario that SignalR does not support very nicely is when the server needs to query its clients for information and have that data returned to the Hub. As currently implemented, a SignalR client can only define handlers for methods called by the Hub which are Action<T,...> delegates, which have a void return signature. So, while the code below will compile if it is included in a Hub method, data is actually a Task object, which is an asynchronous wrapper for the client-side Action. In short, there is no way to directly return a value from a client-side method back to the Hub.

var data = Clients.Client(SOME_ID).GetData()

Why would you want to do this kind of action using SignalR? In my case, I recently encountered a situation where a third-party library implemented a wrapper around a COM application. This COM wrapper was implemented as a singleton, but in my particular case, I needed to execute multiple instances of the underlying COM application, in order to connect to an external service with different user accounts, but, because the wrapper was a singleton, I could only make one connection from my main application process. However, I could get around this limitation by spawning an additional process for each instance of the COM wrapper that was required (fortunately, it was simply a C# singleton, and not limited by any kind of global mutex). While each child process could for the most part operate independently performing its responsibilities, the main application needed to be able to issue commands to control the child processes, as well as retrieve information from them for various reporting features and to ensure consistency in various aspects globally across the application.

I considered using a more traditional IPC method, like bidirectional sockets, or named pipes, but the simplicity and cleanness of using SignalR was more attractive to me than rolling my own message-passing protocol and coding a message loop on both sides. At least for issuing commands to the child processes, implementing a SignalR Hub on my main application to which the clients would connect was pretty quick and easy. Then I ran into the difficulty of how to get data back out of the child processes using SignalR.

In retrospect, I could have modified the code executing on the child processes into a more event-driven form, so that I could use the normal SignalR paradigm to push information to the Hub in the main application, instead of querying the child processes for it. However, this would have involved a complete revision of the architecture of the application (there were very similar components that ran within the main process, using the same interfaces and the bulk of the implementation), as well as necessitating an extensive amount of caching in the main process.

Instead, I settled on the slightly crazy pattern illustrated here. In broad strokes, what I am doing is:

  1. A specially-tagged SignalR client owned by the main application calls a method on the Hub requesting a piece of data from a specific child process.
  2. The Hub resolves the requested child process to its SignalR connection Id, and calls a method to order the child process to assemble the requested data.
  3. The child process calculates the requested data, and pushes it to a different method on the Hub.
  4. The second Hub method stuffs the pushed data from the child process into a dictionary, keyed by the child process' identity.
  5. While this has been happening, the first Hub method has been blocking, waiting for the requested data to appear in the dictionary.
  6. When the data appears in the dictionary, it is popped off, removing the key from the dictionary, preparing the dictionary for the next invocation. There is also a timeout value, so that if there is some sort of interuption, a default value will be returned, rather than indefinitely blocking.
  7. Finally, the first Hub method returns the piece of data to the main application.

bidirectional signalR

The code for this example is available on GitHub at https://github.com/ericrrichards/crazy-signalr-ipc

A Simple Example

To illustrate how this pattern works, we're going to create a server class, a client class, and a simple Hub class for them to communicate via. Our clients identify themselves by an integer ID, and they also possess a more user-friendly name member. Our server spins up the SignalR Hub, then spawns off four client processes, waits a few seconds, then queries each in turn for it's name. When it is time for the application to terminate, the server also instructs the clients to terminate themselves. The Hub sits in the middle of the two, and handles mapping the clients' IDs to their SignalR connections, and passes messages between the two.

IpcHub

We'll start by defining our Hub, IpcHub. The first thing we will do is implement the virtual methods of the SignalR Hub class that handle connection lifetime events, OnConnected, OnDisconnected, and OnReconnected. These are invoked in the following situations:

  • OnConnected: When the client connects to the hub, via HubConnection.Start() or in JavaScript via $.connection.hub.start().
  • OnDisconnected: When the client explicitly disconnects via HubConnection.Stop()(C#) or $.connection.hub.stop(); (JS), or when navigating to a new web page with the JavaScript client.
  • OnReconnected: When a client experiences a temporary break in connectivity with the server, which does not exceed the configured timeout value.
When a client connects to the Hub, it supplies an application-specific identifier through the querystring. In this instance, we are assuming that the server application does not provide an identifier; in a more real situation you may want to require a stronger identification and authentication scheme for your main application. For our client instances, we cache the SignalR connection ID in a static Dictionary, indexed by the querystring id value. This Dictionary must be static, because SignalR hubs are transient - similar to ASP.NET MVC controllers they are instantiated anew to handle each incoming request and then destroyed, so any non-static state is not persisted.

We'll ignore the _shutdownOrderReceived member for the moment.

public class IpcHub : Hub {
    // A map between the child clients and their SignalR connection Ids
    private static readonly ConcurrentDictionary<int, string> ConnectionMap = new ConcurrentDictionary<int, string>();
    private static bool _shutdownOrderReceived;
    // Invoked when a client connects to the hub
    public override Task OnConnected() {
    if (Context.QueryString["id"] == null) {
    // The server doesn't have an id in this case
    // in real usage you may want to authenticate the server in some way
            Console.WriteLine("Server connected");
            _shutdownOrderReceived = false;
    return base.OnConnected();
        }
    // When a client connects, we cache the SignalR connection ID in a dictionary that maps the client ID to the connection ID
        var clientId = Convert.ToInt32(Context.QueryString["id"]);
        Console.WriteLine("Client {0} connected", clientId);
        ConnectionMap[clientId] = Context.ConnectionId;
    return base.OnConnected();
    }
    // Invoked when a client disconnects from the hub
    public override Task OnDisconnected(bool stopCalled) {
    if (Context.QueryString["id"] == null) {
            Console.WriteLine("Server disconnected");
            _shutdownOrderReceived = true;
            Clients.All.Shutdown();
    return base.OnDisconnected(stopCalled);
        }
        var clientId = Convert.ToInt32(Context.QueryString["id"]);
        Console.WriteLine("Client {0} disconnected", clientId);
    string dontCare;
        ConnectionMap.TryRemove(clientId, out dontCare);
    return base.OnDisconnected(stopCalled);
    }
    // Invoked when a temporary network connection error interrupts a client's connection to the hub
    public override Task OnReconnected() {
    if (Context.QueryString["id"] == null) {
            Console.WriteLine("Server reconnected");
            _shutdownOrderReceived = false;
    return base.OnReconnected();
        }
        var clientId = Convert.ToInt32(Context.QueryString["id"]);
        Console.WriteLine("Client {0} reconnected", clientId);
        ConnectionMap[clientId] = Context.ConnectionId;
    return base.OnReconnected();
    }
    // Other methods to follow...
}

Our next method is a relatively straight-forward SignalR hub method. ShutDown() will be called by our server to signal to the client processes that they should spin down and close themselves. We'll only allow the server client to invoke this method - if a naughty client tries, we'll ignore it.

[UsedImplicitly]
public void ShutDown() {
    if (Context.QueryString["id"] == null) {
        Console.WriteLine("Server sends shutdown order");
        Clients.All.Shutdown();
        _shutdownOrderReceived = true;
    } else {
        Console.WriteLine("Received bad command from client {0} : ShutDown", Context.QueryString["id"]);
    }
}

Our final chunk of code for the hub is the interesting bit. This is the code that performs a query against the client and then returns that data to the server. The flow here is:

  1. The server calls GetName(clientId).
  2. GetName(clientId) maps the clientId passed in to the SignalR connection for the specified client process, and calls the GetName() method defined on the client.
  3. GetResult() then blocks execution until a value for the desired clientId is populated into the ClientNames dictionary.
  4. While this is happening, the client process is calculating the desired value, and then it calls ReturnName(clientId, name).
  5. ReturnName(clientId, name) does some sanity checking, and then inserts the data pushed by the client process into ClientNames.
  6. GetResult() then sees that the value is populated, and removes that value from ClientNames and returns it. If the request takes too long, a default value is returned (in this case null).
  7. The value is then returned to the server.

// A dictionary to temporarily store values posted by the clients before returning them to the server
// the values are indexed by the client ids
private static readonly ConcurrentDictionary<int, string> ClientNames = new ConcurrentDictionary<int, string>();
[UsedImplicitly]
public string GetName(int clientId) {
    if (Context.QueryString["id"] == null) {
    if (ConnectionMap.ContainsKey(clientId)) {
    // order the client to return its name
            Clients.Client(ConnectionMap[clientId]).GetName();
    // block until the name is received from the client
    return GetResult(clientId, ClientNames);
        }
    }
    return null;
}
[UsedImplicitly]
// When the client pushes its data, store the value in the dictionary
public void ReturnName(int clientId, string name) {
    if (clientId == Convert.ToInt32(Context.QueryString["id"])) {
        ClientNames[clientId] = name;
    } else {
        Console.WriteLine("Bad return: qs client id={0}, parameter id={1}", Context.QueryString["id"], clientId);
    }
}
private const int TimeOut = 500;
// block until the requested key is populated in the dictionary
// return a default value if something goes wrong and the client does not push the value within the timeout period
// or if the server has ordered a shutdown before the value is returned
private static T GetResult<T, TKey>(TKey key, ConcurrentDictionary<TKey, T> dataStore) {
    var i = 0;
    while (!dataStore.ContainsKey(key) && !_shutdownOrderReceived && i < TimeOut) {
        Thread.Sleep(10);
        i++;
    }
    if (dataStore.ContainsKey(key)) {
        T ret;
        dataStore.TryRemove(key, out ret);
    return ret;
    }
    return default(T);
}

There are some variations that could be made on this idea. A simple change would be, instead of removing the returned value from ClientNames each time in GetResult, simply return whatever value is present. This might be more effective if it is not particularly important that the value returned to the server is absolutely up-to-date, for instance if the data is only being used to populate some informational UI.

Another alternative strategy might be to replace the Dictionary and while(){Thread.Sleep()} loop with a more event-driven design. This would probably still require an AutoResetHandle to signal that a value was available, but would involve less static state.

The Client

Our Client class is very simple. As mentioned earlier, a Client has a clientId to identify it, and a name property. When the Client starts, it constructs a querystring, including its identifying clientId, then creates a connection to the SignalR hub. We then create an IHubProxy for the IpcHub, which allows us to register client-side methods that the hub can call. The ShutDown method simply closes the SignalR connection and sets a flag to break the loop which keeps the client alive. The GetName method pushes the Client's name field to the hub by invoking the ReturnName hub method. Finally, we start the hub connection, and then wait until the hub orders the client to shutdown.

public class Client {
    private readonly string _name;
    private readonly int _clientId;
    public Client(int clientId) {
        _clientId = clientId;
        _name = "Client " + clientId;
    }
    public void Run() {
        var running = true;
    // construct the query string
        var queryStringData = new Dictionary<string, string> {
            {"id", _clientId.ToString()}
        };
        var hubConnection = new HubConnection("http://localhost:8080/", queryStringData);
        var proxy = hubConnection.CreateHubProxy("IpcHub");
    // wire up the client method handlers
        proxy.On("ShutDown", () => {
            Console.WriteLine("Shutdown order received");
    try {
                hubConnection.Stop();
            } finally {
                running = false;
            }
        });
        proxy.On("GetName", () => {
            Console.WriteLine("Name requested by server");
            proxy.Invoke("ReturnName", _clientId, _name);
        });
    // start the connection to the hub
        hubConnection.Start().Wait();
        Console.WriteLine("Connected to server");
    while (running) {
            Thread.Sleep(1000);
        }
    }
}

The Server

The Server class is also very simple. Its purpose is to initialize SignalR, by calling WebApp.Start<Startup>(), connect to the hub, so that we can issue commands, then spawn off a number of Client processes (we'll see exactly how this works shortly). The server then waits a few seconds, and then requests the name of each Client process, by invoking the GetName(clientId) method on the hub. Finally, after the user presses enter to exit, it invokes the ShutDown method on the hub to close the client processes, and then waits until they have exited.

public class Server {
    private readonly List<Process> _clients;
    private readonly int _numClients;
    public Server(int numClients) {
        _numClients = numClients;
        _clients = new List<Process>(numClients);
    }
    public void Run() {
    // start SignalR listening
    using (WebApp.Start<Startup>("http://+:8080/")) {
    // connect to the hub with the special server SignalR connection
            var hubConnection = new HubConnection("http://localhost:8080/");
            var proxy = hubConnection.CreateHubProxy("IpcHub");
            proxy.On("ShutDown", () => Console.WriteLine("Shutdown order received"));
            hubConnection.Start().Wait();
                
    // Spin up some client processes
    for (var i = 0; i < _numClients; i++) {
                var startInfo = new ProcessStartInfo(Assembly.GetExecutingAssembly().ManifestModule.Name) {Arguments = i.ToString()};
                var p1 = new Process {StartInfo = startInfo};
                Console.WriteLine("Starting client " + i);
                p1.Start();
                _clients.Add(p1);
            }
    // wait a bit
            Thread.Sleep(5000);
    // Request the names from the client processes
    for (var i = 0; i < _numClients; i++) {
                Console.WriteLine("Getting Name from client " + i);
                Console.WriteLine(proxy.Invoke<string>("GetName", i).Result);
            }
    // Spin down the clients after the user presses enter
            Console.WriteLine("Press enter to shutdown...");
            Console.ReadLine();
            proxy.Invoke("ShutDown");
    while (_clients.Any(p => !p.HasExited)) {
                Thread.Sleep(100);
            }
            Console.WriteLine("Press enter to exit...");
            Console.ReadLine();
        }
    }
}

Main, and the OWIN Startup Class

As you may have noticed earlier, we spawn the child client processes by launching new instances of the same process which runs our Server application, except with an argument for the clientId. This is a very quick and dirty method, but it works for this example. In a real application, like the one that inspired this example, I would have a special-purpose executable for the child processes.

The final bit is the Startup class. SignalR is built to integrate with OWIN, a newer web interface for .NET. There are a couple of different ways of configuring OWIN, in this case we are using the self- hosted variant (with WebApp.Start<Startup>()). Basically, the OWIN Startup class is where you define any OWIN components that should run as part of your application and configure them. For this example, we are only using SignalR, which has an incredibly simple configuration, assuming that all of the defaults are acceptable for your application: simple call app.MapSignalR(), and you are all done.

static class Program {
    static void Main(string[] args) {
        if (args.Length == 0) {
            var server = new Server(4);
            server.Run();
        } else {
            var clientId = Convert.ToInt32(args.FirstOrDefault());
            var client = new Client(clientId);
            client.Run();
        }
    }
}
public class Startup {
    [UsedImplicitly]
    public void Configuration(IAppBuilder app) {
        app.MapSignalR();
    }
}

running the example

Wrapping Up

Is this a crazy thing to be doing? Probably. But it works, and it has been, in my experience, fairly robust and performant. I have shipped production code using this pattern, and it hasn't come back to bite me yet. If you have any ideas for improvements on the pattern, please leave a note in the comments.


Bookshelf

Hi, I'm Eric, and I'm a biblioholic. Here is a selection of my favorites. All proceeds go to feed my addiction...