我一直在研究一个使用WCF访问服务器端逻辑和数据库的WPF应用程序.
我开始使用一个WCF客户端代理对象,我反复使用它来调用服务器上的方法.使用代理一段时间后,服务器最终会抛出异常:
System.ServiceModel.EndpointNotFoundException: There was no endpoint listening at http://.../Service/BillingService.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details. ---> System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full
.
我认为这是因为每个服务调用都是从代理打开一个新的套接字到服务器,从不关闭它们.最终服务器被淹没并开始拒绝请求.
经过短暂的搜索,我确定我需要定期关闭()代理.我发现的样本简并很小.这个提供了一些有用的提示,但并没有真正回答这个问题.我也看到了避免使用using()模式的建议(并改为使用try/catch/finally),因为代理的Dispose方法可能抛出异常(yuck).
似乎推荐的模式正如此形成:
[TestClass]
public class WCFClientUnitTest
{
BillingServiceClient _service;
[TestMethod]
public void TestGetAddressModel()
{
List<CustomerModel> customers = null;
try
{
_service = new BillingServiceClient();
customers = _service.GetCustomers().ToList();
}
catch
{
_service.Abort();
_service = null;
throw;
}
finally
{
if ((_service != null) &&
(_service.State == System.ServiceModel.CommunicationState.Opened))
_service.Close();
_service = null;
}
if (customers != null)
foreach (CustomerModel customer in customers)
{
try
{
_service = new BillingServiceClient();
AddressModel address = (AddressModel)_service.GetAddressModel(customer.CustomerID);
Assert.IsNotNull(address, "GetAddressModel returned null");
}
catch
{
_service.Abort();
_service = null;
throw;
}
finally
{
if ((_service != null) &&
(_service.State == System.ServiceModel.CommunicationState.Opened))
_service.Close();
_service = null;
}
}
}
Run Code Online (Sandbox Code Playgroud)
所以我的问题仍然围绕着我应该将客户端代理保持多久?我应该为每个服务请求打开/关闭它吗?这对我来说似乎太过分了.我不会受到重大影响吗?
我真正想要做的是创建和打开一个频道,并在整个频道上进行重复,短暂,顺序的服务呼叫.然后很好地关闭频道.
作为旁注,虽然我还没有实现它,但我很快就会在服务(SSL和ACL)中添加一个安全模型来限制谁可以调用服务方法.这篇文章的答案之一提到重新协商身份验证和安全上下文会使每个服务调用的频道重新打开浪费,但只是建议避免构建安全上下文.
编辑11/3/2010:这似乎很重要,所以我将它添加到问题中......
为了回应Andrew Shepherd的评论/建议,我在监控netstat -b的输出时,使用TrendMicro AntiVirus关闭重新运行我的单元测试.Netstat能够记录WebDev.WebServer40.exe拥有的开放端口的显着增长.绝大多数端口都处于TIME_WAIT状态.微软表示客户端关闭连接后端口可能会在NET_WAIT中停留...
注意:套接字处于TIME_WAIT状态很长一段时间是正常的.RFC793中将时间指定为最大段寿命(MSL)的两倍.MSL指定为2分钟.因此,套接字可能处于TIME_WAIT状态长达4分钟.一些系统为MSL实现不同的值(少于2分钟).
这让我相信,如果每个服务调用在服务器上打开一个新的套接字,并且因为我在一个紧密的循环中调用该服务,我可能很容易充斥服务器,导致它耗尽可用的套接字,并反过来生成例外我在上面提到过.
因此,我需要追求以下两种途径之一:1)尝试批量服务调用,以便他们重用服务器端套接字2)更改我的服务合同,以便我可以用更少的调用返回更大的数据块.
第一选择对我来说似乎更好,我将继续追求它.我会回复我发现的内容,欢迎进一步的评论,问题和答案.
(发布完全不同的第二个答案)
如果我们谈论的是"最佳实践",那么WCF的最佳实践就是采用"粗粒度方法".如果客户端在循环中多次调用方法,则应将整个业务逻辑移动到服务本身.
例如.
[DataContract]
class CustomerAndAddress
{
[DataMember]
CustomerModel Customer;
[DataMember]
AddressModel Address;
}
[ServiceContract]
class BillingService
{
[OperationContract]
CustomerAndAddress[] GetAllCustomersAndAddresses();
}
Run Code Online (Sandbox Code Playgroud)
或者,更可能在现实世界中:
[ServiceContract]
class BillingService
{
[OperationContract]
CustomerReportData FetchCustomerReportInfo(CustomerReportParameters parameterSet);
}
Run Code Online (Sandbox Code Playgroud)
话虽如此,我仍然有兴趣看看你是否可以完成你的尝试.
这里似乎有几件事在起作用。
\n\n首先,我使用 netstat \xe2\x80\x93b 运行和监视的单元测试指出Cassini (WebDev.WebServer40.exe) 是累积在 TIME_WAIT 状态的端口的所有者。正如引用的 MSFT 知识库文章所述,在 FIN 握手之后,当应用程序等待网络上任何慢速数据包传送以及消息队列耗尽时,端口停留是正常的。2 分钟的默认配置解释了为什么我在单元测试完成后看到端口欺骗。虽然可以通过注册表设置更改 MSL,但不建议\xe2\x80\x99。
\n\n但是,我几乎忽略的重要一点是该服务是在卡西尼号下运行的。当我将服务器端点切换到在 IIS7 下运行时,我根本没有遇到任何端口增长!我无法解释这是否意味着客户端正在重用端口,或者 IIS7 在端口完成后清理端口方面是否比 Cassini 更好。\n现在,这并不能完全回答我的问题。关于我应该多久关闭 WCF 代理的问题。这只是意味着我不必频繁关闭代理。
\n\n我可以看到,要保持代理长时间打开,仍然需要进行资源权衡。
\n\n如果您有许多(即数千个)客户端访问您的 WCF 服务,则在调用之间或小批量调用之间释放服务器资源可能是有意义的。在这种情况下,请务必使用 try/catch/finally 机制而不是 using(),因为即使服务代理实现了 IDisposable,如果服务处于故障状态,close() 方法也可能会引发异常。
\n\n另一方面(就像我的特定情况一样),如果您只希望有几个客户端访问您的 WCF 服务,则您不需要频繁且显式地打开和关闭服务代理,从而增加复杂性。因此,我打算在应用程序启动时打开代理一次,并保持打开状态直到应用程序完成。我打算实现一个服务调用帮助程序方法(类似于: SCT 过期时续订 WCF 客户端?),该方法将回收连接,以防万一它进入故障状态。然后,我\xe2\x80\x99不必担心管理代理生命周期。
\n\n如果您认为我误读了测试结果,或者您有更好的解决方案,请告诉我。
\n