是否可以为ASP.NET Core中的每个请求创建一个新的WCF客户端,从而导致套接字耗尽? [英] Can creating a new WCF client for each request in ASP.NET Core lead to socket exhaustion?

查看:41
本文介绍了是否可以为ASP.NET Core中的每个请求创建一个新的WCF客户端,从而导致套接字耗尽?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我看来,像开箱即用地使用WCF客户端可能会导致套接字耗尽或我丢失了某些东西?

WCF客户端继承自 ClientBase< TChannel> .阅读此文章在我看来,WCF客户端使用HttpClient.如果是这种情况,那么我可能不应该为每个请求都创建一个新客户端,对吗?

我发现了几篇文章()讨论使用单例或以某种方式重用WCF客户端.这是要走的路吗?

更新

调试WCF源代码的相应部分,我发现每次创建新的WCF客户端(针对每个请求执行一次)时,都会创建一个新的HttpClient和HttpClientHandler.您可以检查代码问题中写道可以将您自己的HttpMessage工厂注入WCF客户端.他写道:

我实现了提供Func的功能,从而可以修改或替换HttpMessageHandler.您提供的方法需要HttpClientHandler并返回HttpMessageHandler.

使用这些信息,我注入了自己的工厂,以便能够控制HttpClient中HttpClientHandlers的生成.

我创建了自己的IEndpointBehavior实现,该实现将IHttpMessageHandlerFactory注入以获得池化的HttpMessageHandler.

 公共类MyEndpoint:IEndpointBehavior{私有只读IHttpMessageHandlerFactory messageHandlerFactory;公共MyEndpoint(IHttpMessageHandlerFactory messageHandlerFactory){this.messageHandlerFactory = messageHandlerFactory;}公共无效的AddBindingParameters(ServiceEndpoint端点,BindingParameterCollection bindingParameters){Func< HttpClientHandler,HttpMessageHandler>myHandlerFactory =(HttpClientHandler clientHandler)=>{返回messageHandlerFactory.CreateHandler();};bindingParameters.Add(myHandlerFactory);}<实现IEndpointBehavior所需的其他空方法>} 

您可以在AddBindingParameters中看到,我添加了一个非常简单的工厂,该工厂返回池化的HttpMessageHandler.

我将这种行为添加到我的WCF客户端中.

 公共类TestService{私有只读MyEndpoint端点;公共TestService(MyEndpoint端点){this.endpoint =端点;}公共异步Task< int>加(int a,int b){CalculatorSoapClient客户端=新的CalculatorSoapClient();client.Endpoint.EndpointBehaviors.Add(endpoint);var resultat =等待client.AddAsync(a,b);//这是关闭客户端的一种不好的方法,我也应该检查//如果我需要调用Abort()等待client.CloseAsync();返回结果}} 

使用这些更改运行应用程序时,我不再对我提出的每个请求都保持开放连接.

解决方案

我创建了 issue 在Github的WCF存储库中,并得到了不错的答案.

根据该领域的权威Matt Connew和Stephen Bonikowsky的说法,最好的解决方案是重用客户端或ChannelFactory.

Bonikowsky写道:

创建一个客户端并重复使用.

  var client = new ImportSoapClient(); 

Connew添加:

另一种可能性是,您可以通过以下方式创建频道代理实例潜在的渠道工厂.您可以使用类似于以下代码这个:

  public void Init(){_client?.Close();_factory?.Close();_client = new ImportSoapClient();_factory = client.ChannelFactory;}公共无效的DoWork(){var proxy = _factory.CreateChannel();proxy.MyOperation();((IClientChannel)代理).Close();} 

根据Connew的说法,使用潜在的并发请求重用ASP.NET Core Web应用程序中的客户端没有问题.

所有并发请求都使用同一个客户端,只要没有问题就可以了在您发出任何请求之前,您明确地打开了频道.如果使用从渠道工厂创建的渠道,您可以使用(((IClientChannel)proxy).Open();..我相信生成的客户也添加您可以使用的OpenAsync方法.

更新

因为重用WCF客户端也意味着重用HttpClient实例,这可能导致已知的 DNS问题我决定按照问题中所述使用我自己的IEndpointBehavior实现使用原始解决方案.

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 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;
    }
}

When I run the applications with these changes I no longer have an open connection for every request I make.

解决方案

I created an issue in the WCF repository in Github and got som 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.

这篇关于是否可以为ASP.NET Core中的每个请求创建一个新的WCF客户端,从而导致套接字耗尽?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆