I've set up a sample SignalR hub, ChatHub, added list of connections. When it runs OnConnected I see it being add to the List. However when I open that page in another browser (expecting the list to have 2 connections now I see 0 connections in my list). Is ChatHub instantiated per request?
List<string> connections = new List<string>();
public override Task OnConnected()
{
connections.Add(Context.ConnectionId);
return base.OnConnected();
}
Yes a Hub instance is created for each request.
specifically:
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.
Related
I am writing a streaming component by sending mediarecorder blob(event.data) to a SignalR hub and return the same blob to other users connected using SendAsync method. But, the object I receive from hub is different from what I passed to hub. Because, my client side JS code does not recognize it like an event.data object.
My question is how the object will change after passing to hub? Here is the hub code and how I can turn it to initial value after receiving back from hub
public async Task SendBlob(Object blob)
{
await Clients.All.SendAsync("ReceivingBlob", blob);
}
This article shows a well-known problem with HttpClient that can lead to socket exhaustion.
I have an ASP.NET Core 3.1 web application. In a .NET Standard 2.0 class library I've added a WCF web service reference in Visual Studio 2019 following this instructions.
In a service I'm using the WCF client the way it's described in the documentation. Creating an instance of the WCF client and then closing the client for every request.
public class TestService
{
public async Task<int> Add(int a, int b)
{
CalculatorSoapClient client = new CalculatorSoapClient();
var resultat = await client.AddAsync(a, b);
//this is a bad way to close the client I should also check
//if I need to call Abort()
await client.CloseAsync();
return resultat;
}
}
I know it's bad practice to close the client without any checks but for the purpose of this example it does not matter.
When I start the application and make five requests to an action method that uses the WCF client and then take a look at the result from netstat I discover open connections with status TIME_WAIT, much like the problems in the article above about HttpClient.
It looks to me like using the WCF client out-of-the-box like this can lead to socket exhaustion or am I missing something?
The WCF client inherits from ClientBase<TChannel>. Reading this article it looks to me like the WCF client uses HttpClient. If that is the case then I probably shouldn't create a new client for every request, right?
I've found several articles (this and this) talking about using a singleton or reusing the WCF client in some way. Is this the way to go?
###UPDATE
Debugging the appropriate parts of the WCF source code I discovered that a new HttpClient and HttpClientHandler were created each time I created a new WCF client which I do for every request.
You can inspect the code here
internal virtual HttpClientHandler GetHttpClientHandler(EndpointAddress to, SecurityTokenContainer clientCertificateToken)
{
return new HttpClientHandler();
}
This handler is used in to create a new HttpClient in the GetHttpClientAsync method:
httpClient = new HttpClient(handler);
This explains why the WCF client in my case behaves just like a HttpClient that is created and disposed for every request.
Matt Connew writes in an issue in the WCF repo that he has made it possible to inject your own HttpMessage factory into the WCF client.
He writes:
I implemented the ability to provide a Func<HttpClientHandler,
HttpMessageHandler> to enable modifying or replacing the
HttpMessageHandler. You provide a method which takes an
HttpClientHandler and returns an HttpMessageHandler.
Using this information I injected my own factory to be able to control the generation of HttpClientHandlers in HttpClient.
I created my own implementation of IEndpointBehavior that injects IHttpMessageHandlerFactory to get a pooled HttpMessageHandler.
public class MyEndpoint : IEndpointBehavior
{
private readonly IHttpMessageHandlerFactory messageHandlerFactory;
public MyEndpoint(IHttpMessageHandlerFactory messageHandlerFactory)
{
this.messageHandlerFactory = messageHandlerFactory;
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
Func<HttpClientHandler, HttpMessageHandler> myHandlerFactory = (HttpClientHandler clientHandler) =>
{
return messageHandlerFactory.CreateHandler();
};
bindingParameters.Add(myHandlerFactory);
}
<other empty methods needed for implementation of IEndpointBehavior>
}
As you can see in AddBindingParameters I add a very simple factory that returns a pooled HttpMessageHandler.
I add this behavior to my WCF client like this.
public class TestService
{
private readonly MyEndpoint endpoint;
public TestService(MyEndpoint endpoint)
{
this.endpoint = endpoint;
}
public async Task<int> Add(int a, int b)
{
CalculatorSoapClient client = new CalculatorSoapClient();
client.Endpoint.EndpointBehaviors.Add(endpoint);
var resultat = await client.AddAsync(a, b);
//this is a bad way to close the client I should also check
//if I need to call Abort()
await client.CloseAsync();
return resultat;
}
}
Be sure to update any package references to System.ServiceModel.* to at least version 4.5.0 for this to work. If you're using Visual Studio's 'Add service reference' feature, VS will pull in the 4.4.4 versions of these packages (tested with Visual Studio 16.8.4).
When I run the applications with these changes I no longer have an open connection for every request I make.
You should consider disposing your CalculatorSoapClient. Be aware that a simple Dispose() is usually not enough, becaue of the implementation of the ClientBase.
Have a look at https://learn.microsoft.com/en-us/dotnet/framework/wcf/samples/use-close-abort-release-wcf-client-resources?redirectedfrom=MSDN, there the problem is explained.
Also consider that the underlying code is managing your connections, sometimes it will keep them alive for later use. Try calling the server a lot of times to see, if there is a new connection for each call, or if the connections are being reused.
The meaning TIME_WAIT is also discussed here:
https://superuser.com/questions/173535/what-are-close-wait-and-time-wait-states
https://serverfault.com/questions/450055/lot-of-fin-wait2-close-wait-last-ack-and-time-wait-in-haproxy
It looks like your client has done everything required to close the connection and is just waiting for the confirmation of the server.
You should not have to use a singleton since the framework is (usually) taking good care of the connections.
I created an issue in the WCF repository in Github and got some great answers.
According to Matt Connew and Stephen Bonikowsky who are authorities in this area the best solution is to reuse the client or the ChannelFactory.
Bonikowsky writes:
Create a single client and re-use it.
var client = new ImportSoapClient();
And Connew adds:
Another possibility is you could create a channel proxy instance from
the underlying channelfactory. You would do this with code similar to
this:
public void Init()
{
_client?.Close();
_factory?.Close();
_client = new ImportSoapClient();
_factory = client.ChannelFactory;
}
public void DoWork()
{
var proxy = _factory.CreateChannel();
proxy.MyOperation();
((IClientChannel)proxy).Close();
}
According to Connew there is no problem reusing the client in my ASP.NET Core web application with potentially concurrent requests.
Concurrent requests all using the same client is not a problem as long
as you explicitly open the channel before any requests are made. If
using a channel created from the channel factory, you can do this with
((IClientChannel)proxy).Open();. I believe the generated client also
adds an OpenAsync method that you can use.
UPDATE
Since reusing the WCF Client also means reusing the HttpClient instance and that could lead to the known DNS problem I decided to go with my original solution using my own implementation of IEndpointBehavior as described in the question.
For SignalR 2.1, how do I maintain a list of Client methods the Server's Hub can call?
According to Differences between SignalR and ASP.NET Core SignalR - Hub Proxies, "Hub proxies are no longer automatically generated."
Is there an existing solution to maintain a list of Client methods the Server's Hub can call?
Looking for a solution that defines Client methods to be called by Server Hub before we decide to roll our own with Code Generation.
Looks like Hub and IHubContext take T type parameter for the type of client that you can make an interface for. Can't find any documentation specific to dotnet core other than the source code and comments but look like this is a carry over from .net
https://blog.scottlogic.com/2014/08/08/signalr-typed.html -> "Calling client hubs - New and Improved"
public interface IMyHubClient
{
void Ping();
}
public class MyHub : Hub<IMyHubClient>
{
...
}
We have configured the redis backplane in owin startup code of the web app.
var config = new RedisScaleoutConfiguration(hangfireRedisConnection, scaleoutEventKey);
GlobalHost.DependencyResolver.UseRedis(config);
app.MapSignalR();
The web app runs a background job on a timer, that wakes up and notifies the client if an important event took place. After querying the backend, the background job calls into a simple HubService wrapper that takes IHubContext as a dependency and eventually calls Client.All.notify method on the hub to push down to the client:
HubService:
private readonly IHubContext applicationHub;
public HubService(IHubContext applicationHub)
{
this.applicationHub = applicationHub;
}
public void NotifyClient()
{
hubContext.Client.All.nofify(message); // <- this is always called, with and without the backplane, however with the backplane it doesn't make it to the client
}
HubContext is registered at the startup:
Container.RegisterInstance<IHubContext>(GlobalHost.ConnectionManager.GetHubContext<ApplicationHub>());
This works fine without the backplane, but doesn't with the backplane configured. I have verified in the debugger that the call is being made, but it doesn't make it down to the client.
Also, if we call signalr hub from the client (outside of the web app) either through js or signalr.client clients, the backplane works as advertised.
Is there something missing in the scenario where we're calling directly into hub context from within the web app itself without initiating the call from the client first?
I am new to signalR and reading the API and playing around with it. a bit confused about the Hub and its Context.
That is, Hub.Context is not HubContext.
HubContext I can get from GlobalHost.ConnectionManager.GetHubContext<THub>()
and Hub.Context gives me a HubCallerContext which I am not sure how to use.
What are their relationship? How can I get HubContext from Hub or Hub from HubContext ?
A result of poor naming. Hub.Context is the HTTP context from the caller (more like a request context). The HubContext has the GroupManager and Clients which map to Hub.Groups and Hub.Clients.
You can add to groups and talk to the client from outside the hub. Inside the hub you can get the connection ID of the caller and get the HTTP request context associated with the hub invocation. Outside of the hub, you can't do Context.Clients.Caller or Context.Clients.Others because there's no caller when you're outside of the hub.
Hope that clears things up.
The HubCallerContext is a context that is relative to the current request. You would not be able to do the following using a HubContext:
public class MyHub : Hub
{
public void Foo()
{
// These two are equivalent
Clients.Caller.bar();
Clients.Client(Context.ConnectionId).bar(); // Context.ConnectionId is the client that made the request connection id
}
}
The reason why you would not be able to do these with the HubContext is because you do not have a Clients.Caller and you do not have a Context.ConnectionId.
You can however do everything that you can do with a HubContext with a HubCallerContext.
Think of a HubCallerContext as a request relative, more advanced version of HubContext.
Ultimately HubContext is used when you want to send data to a hubs clients outside of the context of a request.
Hope this helps!