我一直在研究一个使用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
。
我认为这是因为每个服务调用都是从代理打开一个新的套接字到服务器,而不是关闭它们。最终服务器被淹没并开始拒绝请求。
经过短暂的搜索,我确定我需要定期关闭()代理。我发现的样本简并很小。 This one提供了一些有用的提示,但并没有真正回答这个问题。我还seen recommendations以避免使用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;
}
}
}
所以我的问题仍然围绕着我应该将客户端代理保持多久?我应该为每个服务请求打开/关闭它吗?这对我来说似乎过分了。我不会受到重大影响吗?
我真正想做的是创造&amp;打开一个频道,并在整个频道上进行重复,短暂,顺序的服务呼叫。然后很好地关闭频道。
作为旁注,虽然我还没有实现,但我很快就会在服务中添加一个安全模型(SSL和ACL),以限制谁可以调用服务方法。 this post的答案之一提到重新协商身份验证&amp;安全上下文使每个服务调用的通道重新打开浪费,但只是建议避免构建安全上下文。
EDIT 11/3/2010:这似乎很重要,所以我将其添加到问题中......
为了回应Andrew Shepherd's评论/建议,我在监控netstat -b的输出时,使用TrendMicro AntiVirus关闭重新运行单元测试。 Netstat能够记录WebDev.WebServer40.exe拥有的开放端口的显着增长。绝大多数端口都处于TIME_WAIT状态。 Microsoft says端口可能在客户端关闭连接后停留在NET_WAIT中...
注意:插入套接字是正常的 TIME_WAIT状态很长一段时间 时间时间在。中指定 RFC793是Maximum Segment的两倍 终身(MSL)。 MSL被指定为 2分钟。所以,套接字可以在一个 TIME_WAIT状态长达4 分钟。一些系统实现 不同的值(不到2分钟) 对于MSL。
这让我相信,如果每个服务调用在服务器上打开一个新的套接字,并且因为我在一个紧密的循环中调用该服务,我可能很容易充斥服务器,导致它耗尽可用的套接字并且转而生成我上面提到的例外。
因此,我需要追求两条路径之一: 1)尝试批量服务调用,以便它们重用服务器端套接字 2)更改我的服务合同,以便我可以用更少的电话返回更大的数据块。
对我来说,第一选择似乎更好,我将继续追求它。我会回复我发现的内容,并欢迎进一步的评论,问题和答案。
答案 0 :(得分:8)
(发布完全不同的第二个答案)
如果我们谈论的是“最佳实践”,那么使用WCF的最佳实践就是采用“粗粒度方法”。如果客户端在循环中多次调用方法,则应将整个业务逻辑移动到服务本身。
例如。
[DataContract]
class CustomerAndAddress
{
[DataMember]
CustomerModel Customer;
[DataMember]
AddressModel Address;
}
[ServiceContract]
class BillingService
{
[OperationContract]
CustomerAndAddress[] GetAllCustomersAndAddresses();
}
或者,更有可能在现实世界中:
[ServiceContract]
class BillingService
{
[OperationContract]
CustomerReportData FetchCustomerReportInfo(CustomerReportParameters parameterSet);
}
话虽如此,我仍然有兴趣看看你是否可以完成你的尝试。
答案 1 :(得分:3)
这里似乎有几件事情在发挥作用。
首先运行并使用netstat -b监视的单元测试指出 Cassini (WebDev.WebServer40.exe)是在TIME_WAIT状态下累积的端口的所有者。正如引用的MSFT kb文章所指出的,当应用程序等待网络上的任何慢速数据包被传递并且消息队列耗尽时,端口在FIN握手之后延迟是正常的。 2分钟的默认配置解释了为什么我在单元测试完成后看到端口装满了。虽然可以通过注册表设置更改MSL,但不建议这样做。
但是,我几乎忽略的重要一点是该服务是在Cassini下运行的。当我将服务器端点切换到在IIS7下运行时,我根本没有任何端口增长!我无法解释这是否意味着客户端正在重用端口,或者IIS7在完成后清理端口是否比Cassini更好。 现在,这并不能完全回答我关于应该多久关闭WCF代理的问题。这只是意味着我没有 拥有 来经常关闭代理。
我可以看到,在长时间保持代理开放的情况下仍然存在资源权衡。
如果您有许多(即数千)客户端访问您的WCF服务,那么在两次调用或小批量调用之间释放服务器资源可能是有意义的。在这种情况下,请确保使用try / catch / finally机制而不使用(),因为即使服务代理实现了IDisposable,如果服务处于故障状态,close()方法也会抛出异常。
另一方面(如我的特定情况),如果您只希望有几个客户端访问您的WCF服务,则不需要频繁且明确地打开和关闭服务代理的额外复杂性。因此,我打算在应用程序启动时打开代理,并在应用程序完成之前保持打开状态。我打算实现一个服务调用帮助方法(类似于:Renewing a WCF client when SCT has expired?),它将回收连接,以防万一它进入故障状态。然后,我不必担心管理代理生存期。
如果我认为我误读了我的测试结果,或者您有更好的解决方案,请告诉我。
答案 2 :(得分:3)
我写了(好,找到并修改了)一个包装器来帮助正确处理服务。抱歉VB。它有一些额外的东西,比如MessageViewerInspector
,所以我可以拦截进出服务的XML。暂时忽略它。
编辑:我知道这并没有真正回答您如何维护请求突发的问题,但它肯定会使代码更清晰,更加清晰。
Imports System.ServiceModel
Namespace WCF
''' <summary>
''' This helper fixes an issue with WCF services where an exception gets thrown
''' during the Dispose() call of the client, so it doesn't actually get disposed.
''' </summary>
''' <typeparam name="TProxy"></typeparam>
''' <typeparam name="TChannel"></typeparam>
''' <remarks></remarks>
Public Class ServiceProxyHelper(Of TProxy As {ClientBase(Of TChannel), New}, TChannel As Class)
Implements IDisposable
Private _proxy As TProxy
Private _inspector As MessageViewerInspector
Public ReadOnly Property ServiceProxy() As TProxy
Get
If Not _proxy Is Nothing Then
Return _proxy
Else
Throw New ObjectDisposedException("ServiceProxyHelper")
End If
End Get
End Property
Public ReadOnly Property Inspector() As MessageViewerInspector
Get
If _inspector Is Nothing Then
_inspector = New MessageViewerInspector()
End If
Return _inspector
End Get
End Property
Public Sub New()
_proxy = New TProxy()
_proxy.Endpoint.Behaviors.Add(Me.Inspector)
End Sub
Public Sub New(ByVal endpointAddress As String)
Me.New()
If Not Me._proxy Is Nothing AndAlso Not String.IsNullOrEmpty(endpointAddress) Then
Me._proxy.Endpoint.Address = New EndpointAddress(endpointAddress)
End If
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Try
If Not _proxy Is Nothing Then
If _proxy.State <> CommunicationState.Faulted Then
_proxy.Close()
Else
_proxy.Abort()
End If
End If
Catch ex As CommunicationException
_proxy.Abort()
Catch ex As TimeoutException
_proxy.Abort()
Catch ex As Exception
_proxy.Abort()
Throw
Finally
_proxy = Nothing
End Try
End Sub
End Class
End Namespace
然后你可以像这样使用它:
Using myService As New ServiceProxyHelper(Of MyService.MyServiceClient, MyService.IMyService)
Try
' Do work
myService.ServiceProxy.DoWork()
Catch ex As FaultException(Of MyService.MyServiceException)
' Log exception
End Try
End Using
答案 3 :(得分:0)
你已经在问题中提出了自己的答案。
“我真正想要做的是创建和开放一个频道,并在整个频道上进行重复,短暂,顺序的服务呼叫。然后很好地关闭频道”
这是正确的。
将此应用于您给出的示例,您可以将其简化为:
try
{
_service = new BillingServiceClient();
customers = _service.GetCustomers().ToList();
if (customers != null)
foreach (CustomerModel customer in customers)
{
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;
}
稍后编辑:哦,请继续,我只是重读你的问题。你已经说过多次重复通话后会失败。我猜上面的代码实际上导致了失败?