Edm*_*son 5 .net asp.net wcf web-services
本文介绍了 HttpClient 的一个众所周知的问题,该问题可能导致套接字耗尽。
我有一个 ASP.NET Core 3.1 Web 应用程序。在 .NET Standard 2.0 类库中,我按照此说明在 Visual Studio 2019 中添加了 WCF Web 服务引用。
在服务中,我按照文档中描述的方式使用 WCF 客户端。创建 WCF 客户端的实例,然后针对每个请求关闭客户端。
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;
}
}
Run Code Online (Sandbox Code Playgroud)
我知道在没有任何检查的情况下关闭客户端是不好的做法,但就本示例而言,这并不重要。
当我启动应用程序并向使用 WCF 客户端的操作方法发出五个请求,然后查看 netstat 的结果时,我发现状态为 TIME_WAIT 的打开连接,与上面有关 HttpClient 的文章中的问题非常相似。
在我看来,像这样使用开箱即用的 WCF 客户端可能会导致套接字耗尽,或者我是否遗漏了某些内容?
WCF 客户端继承自ClientBase<TChannel>
. 阅读这篇文章,我觉得 WCF 客户端使用 HttpClient。如果是这种情况,那么我可能不应该为每个请求创建一个新客户端,对吧?
我发现了几篇文章(这篇和这篇)讨论使用单例或以某种方式重用 WCF 客户端。这是要走的路吗?
###更新
调试 WCF 源代码的相应部分,我发现每次我为每个请求创建新的 WCF 客户端时,都会创建一个新的 HttpClient 和 HttpClientHandler。您可以在此处检查代码
internal virtual HttpClientHandler GetHttpClientHandler(EndpointAddress to, SecurityTokenContainer clientCertificateToken)
{
return new HttpClientHandler();
}
Run Code Online (Sandbox Code Playgroud)
此处理程序用于在 GetHttpClientAsync 方法中创建新的 HttpClient:
httpClient = new HttpClient(handler);
Run Code Online (Sandbox Code Playgroud)
这解释了为什么在我的例子中,WCF 客户端的行为就像为每个请求创建和处置的 HttpClient。
Matt Connew 在 WCF 存储库的一个问题中写道,他已经可以将您自己的 HttpMessage 工厂注入到 WCF 客户端中。他写:
我实现了提供 Func<HttpClientHandler, HttpMessageHandler> 的功能,以允许修改或替换 HttpMessageHandler。您提供一个接受 HttpClientHandler 并返回 HttpMessageHandler 的方法。
使用这些信息,我注入了自己的工厂,以便能够控制 HttpClient 中 HttpClientHandler 的生成。
我创建了自己的 IEndpointBehavior 实现,它注入 IHttpMessageHandlerFactory 以获取池化的 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>
}
Run Code Online (Sandbox Code Playgroud)
正如您在 AddBindingParameters 中看到的,我添加了一个非常简单的工厂,它返回池化的 HttpMessageHandler。
我将此行为添加到我的 WCF 客户端,如下所示。
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;
}
}
Run Code Online (Sandbox Code Playgroud)
请务必将所有包引用System.ServiceModel.*
至少更新到版本4.5.0才能正常工作。如果您使用 Visual Studio 的“添加服务引用”功能,VS 将提取这些包的 4.4.4 版本(使用 Visual Studio 16.8.4 进行测试)。
当我运行具有这些更改的应用程序时,我不再为我发出的每个请求建立开放连接。
我创建了一个在 Github 的 WCF 存储库中创建了一个问题,并得到了一些很好的答案。
根据该领域权威 Matt Connew 和 Stephen Bonikowsky 的说法,最好的解决方案是重用客户端或 ChannelFactory。
博尼科夫斯基写道:
创建一个客户端并重复使用它。
var client = new ImportSoapClient();
Run Code Online (Sandbox Code Playgroud)
康纽补充道:
另一种可能性是您可以从底层通道工厂创建通道代理实例。您可以使用类似于以下的代码来执行此操作:
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();
}
Run Code Online (Sandbox Code Playgroud)
根据 Connew 的说法,在具有潜在并发请求的 ASP.NET Core Web 应用程序中重用客户端没有问题。
只要您在发出任何请求之前显式打开通道,所有使用同一客户端的并发请求就不是问题。如果使用从通道工厂创建的通道,则可以使用 ((IClientChannel)proxy).Open(); 来执行此操作。我相信生成的客户端还添加了一个您可以使用的 OpenAsync 方法。
由于重用 WCF 客户端也意味着重用实例HttpClient
,这可能会导致已知的DNS 问题,因此我决定使用我自己的实现(IEndpointBehavior
如问题中所述)采用原始解决方案。