SignalR: client disconnection - c#

How does the SignalR handle client disconnection? Am I right if I state the following?
SignalR will detect browser page close/refresh via Javascript event handling and will send appropriate packet to server (through the persisting connection);
SignalR will NOT detect browser close/network failure (probably only by timeout).
I aim the long-polling transport.
I'm aware of this question but would like to make it a bit clear for me.

If a user refreshes the page, that is treated as a new connection. You are correct that the disconnect is based on a timeout.
You can handle the Connect/Reconnect and Disconnect events in a Hub by implementing SignalR.Hubs.IConnected and SignalR.Hubs.IDisconnect.
The above referred to SignalR 0.5.x.
From the official documentation (currently for v1.1.3):
public class ContosoChatHub : Hub
{
public override Task OnConnected()
{
// Add your own code here.
// For example: in a chat application, record the association between
// the current connection ID and user name, and mark the user as online.
// After the code in this method completes, the client is informed that
// the connection is established; for example, in a JavaScript client,
// the start().done callback is executed.
return base.OnConnected();
}
public override Task OnDisconnected()
{
// Add your own code here.
// For example: in a chat application, mark the user as offline,
// delete the association between the current connection id and user name.
return base.OnDisconnected();
}
public override Task OnReconnected()
{
// Add your own code here.
// For example: in a chat application, you might have marked the
// user as offline after a period of inactivity; in that case
// mark the user as online again.
return base.OnReconnected();
}
}

In SignalR 1.0, the SignalR.Hubs.IConnected and SignalR.Hubs.IDisconnect are no longer implemented, and now it's just an override on the hub itself:
public class Chat : Hub
{
public override Task OnConnected()
{
return base.OnConnected();
}
public override Task OnDisconnected()
{
return base.OnDisconnected();
}
public override Task OnReconnected()
{
return base.OnReconnected();
}
}

Related

SignalR, how to validate if a connection id is still active?

As the title says, how to validate if a connection id is still active using SignalR?
I have something similar as below where I map the connection ids to a user id. The problem is that in rare cases OnDisconnectedAsync does not triggeres.
Then I can't make the feature where the user is joining or leaving because it thinks that the user still have a connection.
I do have a "pinger" which run each 5 minutes that is updating a expire date but it is not reliable.
What I want is something like loop through all connection ids and verify if they are still active.
How can this be done? I thought maybe I can send a message to all connection ids for user X and see if I get something back and then do some kind of cleanup?
public class Chat : Hub
{
private IConnectionManager _manager;
public Chat(IConnectionManager manager)
{
_manager = manager;
}
public override Task OnConnectedAsync()
{
// Add connectionId and any other info you want to your connectionManager
_manager.Add(Context.ConnectionId, Context.User, Context.GetHttpContext());
}
public override Task OnDisconnectedAsync(Exception exception)
{
_manager.Remove(Context.ConnectionId);
}
}
SignalR has its own "pinger".
//
// Summary:
// Gets or sets the interval used by the server to send keep alive pings to connected
// clients. The default interval is 15 seconds.
public TimeSpan? KeepAliveInterval { get; set; }
And you can configure it on Startup like:
services.AddSignalR(hubOptions =>
{
hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(hostConfiguration.SignalR.KeepAliveInterval);
}
So basically if the client will not respond in the defined timespan, it will trigger OnDisconnectedAsync.

SignalR constant stream of data?

I'm quite new to SignalR. I've setup a frontend in React that connects to a SignalR backend which itself connects to a machine via OPC-UA (It's a school project)
The purpose of the program is to stream live data from a PLC to my React frontend. The way I do it now is this: Connect via a button in frontend -> Frontend invokes a method that subscribes to some PLC nodes which then sends the data back.
My problem is that I need to keep the invoked method running with a 'while(true)' loop otherwise I get an error because the Hub object is disposed.
(This makes the PLC call SubscriptionHandler when a node values changes, which gives an error cause the Hub object isnt there anymore I think) -> (new OpcSubscribeDataChange("ns=6;s=::Program:Cube.Command.CntrlCmd", SubscriptionHandler)
How do I keep the connection alive in a proper way without the Hub object being disposed?
This is the SignalR code:
using Microsoft.AspNetCore.SignalR;
using System;
using System.Threading.Tasks;
using System.Diagnostics;
using Opc.UaFx.Client;
using Connections;
namespace StreamBackend.Hubs
{
public class DataHub : Hub
{
private IHubContext<DataHub> _context;
public override async Task OnConnectedAsync()
{
await Groups.AddToGroupAsync(Context.ConnectionId, "ConnectedUsers");
await base.OnConnectedAsync();
Console.WriteLine("Client Connected");
OPC.Client.Connect();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, "SignalR");
await base.OnDisconnectedAsync(exception);
Console.WriteLine("Removed: " + Context.ConnectionId);
}
public DataHub(IHubContext<DataHub> context) {
_context = context;
// Implement check on whether connected already or not
Console.WriteLine("Constructor");
}
public async void SubscriptionHandler(object sender, OpcDataChangeReceivedEventArgs e) {
OpcMonitoredItem item = (OpcMonitoredItem)sender;
var NodeId = Convert.ToString(item.NodeId);
var Value = Convert.ToString(e.Item.Value);
await SendData(NodeId, Value);
}
public async Task SendData(string NodeId, string Value) {
await Clients.All.SendAsync("LatestChange", NodeId, Value);
}
public async Task DataHubConnection()
{
await Clients.All.SendAsync("InvokeMethodFromBackend");
OpcSubscribeDataChange[] nodes = new OpcSubscribeDataChange[] {
new OpcSubscribeDataChange("ns=6;s=::Program:Cube.Command.CntrlCmd", SubscriptionHandler),
new OpcSubscribeDataChange("ns=6;s=::Program:Cube.Command.Parameter[0].Value", SubscriptionHandler)
};
OpcSubscription subscription = OPC.Client.SubscribeNodes(nodes);
// This keeps the "Hub" alive. It is needed, cause it is the SubscriptionHandlers accesspoint
while(true) {
Console.WriteLine("Open");
System.Threading.Thread.Sleep(2000); //Hang out for half a second (testing)
}
}
// 1006 error is when the server closes the connection
}
}
According to the SignalR-Handbook, this is intended behavior:
Hub object lifetime
You don't instantiate the Hub class or call its methods from your own code on the server; all that is done for you by the SignalR Hubs pipeline. SignalR creates a new instance of your Hub class each time it needs to handle a Hub operation such as when a client connects, disconnects, or makes a method call to the server.
Because instances of the Hub class are transient, you can't use them to maintain state from one method call to the next. Each time the server receives a method call from a client, a new instance of your Hub class processes the message. To maintain state through multiple connections and method calls, use some other method such as a database, or a static variable on the Hub class, or a different class that does not derive from Hub. If you persist data in memory, using a method such as a static variable on the Hub class, the data will be lost when the app domain recycles.
You need to write a class, which have OPC Client and OPC subscriptions as members. The class should be inserted into the hub via DependencyInjection, see as example IStockTicker in Dependency Injection in SignalR

Does the SignalR IHubProxy interface expose any properties that indicate that the client is in a disconnected state?

I have a .NET client that creates a proxy to my hub class. When the server hosting that hub goes down for long enough, the client will go into a disconnected state. I want to check if the proxy is in a disconnected state before invoking a hub method, rather than just attempting to invoke the hub method and then catching the error if it's in a disconnected state.
While debugging in Visual Studio I can see the IHubProxy object has a base property State indicating the current state. It says Connected when it's working fine, and it says Disconnected when it's disconnected. However, I can't seem to access this property.
Does anyone know if there is a way to tell? Ideally I'd just like to do something like this:
if (hubProxy.State == ConnectionState.Disconnected)
{
this.AttemptReconnection();
}
if (hubProxy.State == ConnectionState.Connected)
{
await hubProxy.Invoke("MyMethod", myMethodArgs);
}
I realized that the HubConnection class is what I wanted. I had forgotten about that class because I have a proxy service class specifically for creating a proxy to the hub and I made it only expose its IHubProxy property since that is what's used by the client to invoke hub methods. By exposing its HubConnection property as well, the client is able to check the state.
For the sake of completeness of this answer, this is what the bare bones of my client code looks like:
private void ConnectToHub()
{
try
{
// this is a method in the proxy service class that tries to connect to the hub
// it returns true if it was able to connect successfully
this.connected = hubProxyService.AttempConnectionToHub();
if (this.connected)
{
this.hubProxy = hubProxyService.HubProxy;
this.hubConnection = hubProxyService.HubConnection
}
}
catch
{
this.connected = false;
}
}
private void MyMethodThatInvokesHubMethod()
{
// Do some stuff
// ...
// ...
// ...
if (this.hubConnection.State == ConnectionState.Disconnected)
{
this.ConnectToHub();
}
if (this.hubConnection.State == ConnectionState.Connected)
{
await this.hubProxy.Invoke("MyHubMethod", hubMethodArgs);
}
}

Communication between Topshelf service (acting as TCP server) and selfhosted OWIN WebAPI

I have a Topshelf windows service that acts as a TCP server. Inside this service, I also have a self-hosted (OWIN) WebAPI.
My goal is to somehow allow the WebAPI to communicate with the TCP server that's contained and running in the same service. Naturally I could simply use something like a "trigger" file or a shared DB that could be polled frequently, though I'd like to know of any more optimal/native ways to achieve this.
To get a better idea of the project, think of a single page application consuming my API and making certain calls with arbitrary string parameters. This data should then be passed to clients (C++ console apps using winsock) that are connected to the running TCP server.
The following Container is instantiated and passed to the Topshelf HostConfigurator
class ContainerService
{
private APIService _apiService;
private EngineService _engineService;
protected IDisposable WebAppHolder { get; set; }
public bool Start(HostControl hostControl)
{
var host = hostControl;
_apiService = new APIService();
_engineService = new EngineService();
// Initialize API service
if (WebAppHolder == null)
{
WebAppHolder = _apiService.Initialize();
}
// Initialize Engine service
_engineService.Initialize();
return true;
}
public bool Stop(HostControl hostControl)
{
// Stop API service
if (WebAppHolder != null)
{
WebAppHolder.Dispose();
WebAppHolder = null;
}
// Stop Engine service
_engineService.Stop();
return true;
}
}
Standard Topshelf stuff in program entry point (as mentioned above):
HostFactory.Run(hostConfigurator =>
{
hostConfigurator.Service<ContainerService>(containerService =>
{
containerService.WhenStarted((service, control) => service.Start(control));
containerService.WhenStopped((service, control) => service.Stop(control));
});
hostConfigurator.RunAsLocalSystem();
hostConfigurator.SetServiceName("Educe Service Host");
hostConfigurator.SetDisplayName("Communication Service");
hostConfigurator.SetDescription("Responsible for API and Engine services");
});
TCP Server:
public void Initialize()
{
_serverListener = new TcpListener(new IPEndPoint(hostAddress, (int)port));
_serverListener.Start();
_threadDoBeginAcceptTcpClient = new Thread(() => DoBeginAcceptTcpClient(_serverListener));
_threadDoBeginAcceptTcpClient.Start();
}
...
public void DoBeginAcceptTcpClient(TcpListener listener)
{
while(!_breakThread)
{
// Set the event to nonsignaled state.
TcpClientConnected.Reset();
// Start to listen for connections from a client.
Console.WriteLine("Waiting for a connection...");
// Accept the connection.
listener.BeginAcceptTcpClient(DoAcceptTcpClientCallback, listener);
// Wait until a connection is made and processed before continuing.
TcpClientConnected.WaitOne();
}
}
// Process the client connection.
public void DoAcceptTcpClientCallback(IAsyncResult ar)
{
// Get the listener that handles the client request.
TcpListener listener = (TcpListener)ar.AsyncState;
// End the operation and display the received data on the console.
Console.WriteLine("Client connection completed");
Clients.Add(listener.EndAcceptTcpClient(ar));
// Signal the calling thread to continue.
TcpClientConnected.Set();
}
WebAPI Controller:
public class ValuesController : ApiController
{
// GET api/values/5
public string Get(int id)
{
return $"Foo: {id}";
}
}
As mentioned earlier, what I seek is "communication" between the WebAPI and the windows service. How can I pass the "id" parameter from the WebAPI call to the _engineService object in my windows service? Perhaps something similar to WPF's MVVM Light Messenger? The idea is that it would then be parsed and sent to the appropriate TcpClient that is stored in the Clients List.
Any advice on how to achieve this will be appreciated. Please feel free to ask for clarification/more code.
Did you find any answer to your issue yet ?
I don't quite understand what you try to achieve looking for a communication between the two of them ? Do you want to somehow rely on TCP/IP to relay this id or in-memory ?
Potentially, you could consider a Mediator pattern and use this kind of library that seems quite useful in the case I understood : https://github.com/jbogard/MediatR
In a simpler approach, I would rely on events to achieve what you are trying to do, which is having a reactive communication from the HTTP request to the c++ users.
Did I understand you needs ? I am quite curious about the solution
I'm assuming you are trying to take an HTTP GET request's ID parameter and send it to TCP clients who are connected to the EngineService. If your EngineService is initialized before your ApiService, I think this is a question of how to get a handle to the one-and-only EngineService instance from within an ApiService's controller instances.
If I'm following you, you could make the EngineService a public static property of your ContainerService and reference it as ContainerService.EngineService from the controller (or anywhere in the app for that matter) or better register your EngineService as a singleton in a DI container an inject it into the ApiService.
Solution (calls to WebAPI trigger EngineService)
I now use RabbitMQ/EasyNetQ to achieve communication between the WebApi and the EngineService object containing my TCP clients.
I have incidentally split them into two separate Projects/Topshelf services now.
The following is the new "communication" component and it is instantiated in the EngineService constructor.
public class Communication
{
private readonly Logger _logger;
private readonly IBus _bus;
public delegate void ReceivedEventHandler(string data);
public event ReceivedEventHandler Received;
protected virtual void OnReceive(string data)
{
Received?.Invoke(data);
}
public Communication()
{
_logger = new Logger();
_bus = RabbitHutch.CreateBus("host=localhost", reg => reg.Register<IEasyNetQLogger>(log => _logger));
SubscribeAllQueues();
}
private void SubscribeAllQueues()
{
_bus.Receive<Message>("pipeline", message =>
{
OnReceive(message.Body);
});
}
public void SubscribeQueue(string queueName)
{
_bus.Receive<Message>(queueName, message =>
{
OnReceive(message.Body);
});
}
}
An event handler is then added.
This means that as soon as a message arrives to the bus, the data will be relayed to the event handler which will subsequently relay it to the first connected TCP client in the list.
public void Handler(string data)
{
//Console.WriteLine(data);
_clients[0].Client.Send(Encoding.UTF8.GetBytes(data));
}
...
_comPipe.Received += Handler;
And finally on the WebApi's controller:
public string Get(int id)
{
ServiceCom.SendMessage("ID: " + id);
return "value";
}
ServiceCom class. Allows sending a string message on the bus.
public static class ServiceCom
{
public static void SendMessage(string messageBody)
{
var messageBus = RabbitHutch.CreateBus("host=localhost");
messageBus.Send("pipeline", new Message { Body = messageBody });
}
}
Now that this is done, I am now looking to implement a way for the connected TCP clients to trigger updates/events in an additional SPA project that will act as a Portal / Client Management App.
My approach will probably make use of KnockOut.js and SignalR to achieve dynamic Views where TCP client events are displayed immediately and similarly actions on to WebAPI will trigger events in the TCP clients. I know it sounds like a bizarre combination of processes but it is all according to plan and working out as expected :)

Anonymous Private Chat in SignalR

I'm somewhat new to SignalR. I understand hubs to a limited degree, but I don't understand how two users can share a connection while excluding others.
My scenario is that I want an unauthenticated public website user to be able to initiate a private (not necessarily secure) chat session with a customer service user.
Is there an example or resource that my point me in the right direction?
I've looked at a few resources, including http://www.asp.net/signalr/overview/signalr-20/hubs-api/mapping-users-to-connections but haven't found the right scenario.
You can create groups, so add some methods to your hub (a subscribe method should return a Task as they are asynchronous...)
public Task SubscribeToGroup(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
Then you publish notifications to users of that group as normal but via the groups collection...
public void BroadcastMessageToGroup(string groupName, string message)
{
Clients.Group(groupName).onCaptionReceived(message);
}
Now only subscribers of that particular group will get the message!
Hope this helps.
You can find a tutorial here for SignalR Groups.
http://www.asp.net/signalr/overview/signalr-20/hubs-api/working-with-groups
You can create a group in Hub's API, in this method each user is a member of that group. And they send a message to that group ( via the server), and because they are only 2 members they are the only one's who see the messages ( privatly)
You can also message a group member directly by connection ID. This requires your app to keep track of connection IDs of users as they connect and disconnect, but this isn't too difficult:
//Users: stores connection ID and user name
public static ConcurrentDictionary Users = new ConcurrentDictionary();
public override System.Threading.Tasks.Task OnConnected()
{
//Add user to Users; user will supply their name later. Also give them the list of users already connected
Users.TryAdd(Context.ConnectionId, "New User");
SendUserList();
return base.OnConnected();
}
//Send everyone the list of users (don't send the userids to the clients)
public void SendUserList()
{
Clients.All.UpdateUserList(Users.Values);
}
//Clients will call this when their user name is known. The server will then update all the other clients
public void GiveUserName(string name)
{
Users.AddOrUpdate(Context.ConnectionId, name, (key, oldvalue) => name);
SendUserList();
}
//Let people know when you leave (not necessarily immediate if they just close the browser)
public override System.Threading.Tasks.Task OnDisconnected()
{
string user;
Users.TryRemove(Context.ConnectionId, out user);
SendUserList();
return base.OnDisconnected();
}
//Ok, now we can finally send to one user by username
public void SendToUser(string from, string to, string message)
{
//Send to every match in the dictionary, so users with multiple connections and the same name receive the message in all browsers
foreach(KeyValuePair user in Users)
{
if (user.Value.Equals(to))
{
Clients.Client(user.Key).sendMessage(from, message);
}
}
}

Categories

Resources