A requirement of a recent project was to have asynchronous communication between a set of load balanced servers and multiple web clients. SignalR has the ability to work across multiple servers using a SQL server backplane through the NuGet package. I found a good guide on asp.net‘s website to get the server up and running, but all of the examples involved sending messages to all users at the same time.
Clients.All.addNewMessageToPage(name, message);
In my scenario I needed to signal each user individually.
Clients can be sent to different servers behind the load balancer as they browse the site. Consider this scenario:
1. Client A makes a request to be added to the notification list.
2. The load balancer sends Client A to Server 1.
3. Server 1 registers Client A with it’s local instance of SignalR.
4. Client B requests to notify Client A that some event has occurred.
5. The load balancer sends Client B to Server 2.
6. Server 2 is not aware of the connection between Server 1 and Client A, and the request fails.
With the SQL backplane all of the servers share the same connection list, allowing a message to be sent to a Client A from a Server 2, even though Client A registered with Server 1.
In order to send a message to a single client the server needs to know the specific connection ID to send the message to. SignalR doesn’t offer a way to store a connection ID “key value pair”, so I created one on my own.
In my Hub.cs file I added a method that runs during the client registration:
public void registerClient(long SpecificIdentifier) { HubMethods.RegisterClient(SpecificIdentifier, Guid.Parse(Context.ConnectionId)); }
In the RegisterClient method I store the SignalR connection ID along with a identifier specific to my application. A table stores the ID, the SignalR connection ID, and a timestamp. A procedure was created to clear out the table nightly.
public static void RegisterClient(long SpecificIdentifier, Guid ConnectionID) { using (dbEntities db = new dbEntities()) { Notification notification = db.Notifications.Where(x => x.ID == SpecificIdentifier).FirstOrDefault(); if (notification == null) { notification = new Notification() { ID = SpecificIdentifier, ConnectionID = ConnectionID, LastModified = DateTime.UtcNow, }; db.Notifications.Add(notification); } else { notification.ID = SpecificIdentifier; notification.ConnectionID = ConnectionID; notification.LastModified = DateTime.UtcNow; } db.SaveChanges(); } }
With the SQL backplane all of the servers share the same connection list, allowing a message to be sent to a Client A from a Server 2, even though Client A registered with Server 1.
When it comes time to send a message to a client, the notification method simply looks up the SignalR connection ID based on the ID specific to our application. The SQL backplane gathers the required information automatically based on the connection ID and the message is sent.
public static void NotifyClient(long SpecificIdentifier, Hub.Statuses Status) { using (dbEntities = new dbEntities ()) { Notification notification = db.Notifications.Where(x => x.ID == SpecificIdentifier).FirstOrDefault(); if (notification != null) { String message = String.Empty; if (Status== Hub.Statuses.Confirmed) { message = "Here's the message for confirmation"; } else if (Status == Hub.Statuses.Failed) { message = "Here's the message for failure"; } try { IHubContext notificationHub = GlobalHost.ConnectionManager.GetHubContext<Hub>(); notificationHub.Clients.Client(notification.ConnectionID.ToString()).addMessage(message); } catch (Exception ex) { ex.LogToElmah(); } } else { throw new Exception("Unable to find connection"); } } }
hi
I am trying implement signal scaleout in loadbalancer environment.
i am just storing the connectionid and username in sql server on onconnect event.
but not able to send message from client1 to server2 and client 2 from server1.
I want to send the message to specific client based on the connectionid stored in sql server.
please share your code
Regards
sadanand
Not sure what code you’re looking for Sadanand, everything is posted. Perhaps post your own code in a stackoverflow question so I can take a look at it there?
Hi Sadanand, I’m also having the same issue like you. Can you please share how you solved the issue.