当关闭客户端应用程序时,在服务器上记录了ServiceModel错误:现有连接被远程主机强制关闭。

4

我有一个自托管的WCF服务,以及几个客户端进程...一切都运行良好,客户端启动,进行多个服务调用,然后退出。 但是在服务器上,我的错误日志(我从System.ServiceModel中转发错误级别跟踪消息)每次客户端应用程序关闭时都会有一条记录(这与服务方法调用不一致)。

我正在使用.NET 4.5上的自定义TCP绑定...

 <bindings>
  <customBinding>
    <binding name="tcp">
      <security authenticationMode="SecureConversation" />
      <binaryMessageEncoding compressionFormat="GZip" />
      <tcpTransport />
    </binding>
  </customBinding>

客户端继承自 ClientBase,我对客户端调用 Close() 没有问题。在操作过程中创建了多个 ClientBase 实例并将其关闭而没有出现任何错误。
我猜想客户端保留了一个 socket 以供重复使用(这是合理的优化)。然后在应用程序退出时,该 socket 被清除。
这是否表示我需要修复的错误?如果它不是真正的“错误”,那么我能否以某种方式避免这种情况,以便不会在我的错误日志中添加垃圾信息?
客户端绑定配置与服务器完全相同(自然)。以下是我的调用代码...请注意,我使用此问题ServiceHelper类。
using (var helper = new ServiceHelper<ARAutomatchServiceClient, ServiceContracts.IARAutomatchService>())
{
    return await helper.Proxy.GetBatchesAsync(startDate, DateTime.Today.AddDays(5));
}

具体而言,我在服务器上看到的"错误"级别跟踪事件包含以下信息(为简洁起见已清除堆栈跟踪和其他元素):

System.ServiceModel 错误: 131075 :

System.ServiceModel.CommunicationException: 套接字连接被中止。这可能是由于处理消息时出现错误或远程主机超过了接收超时,或底层网络资源问题引起的。

System.Net.Sockets.SocketException: 现有连接被远程主机强制关闭 NativeErrorCode: 2746

1个回答

3
我所见到的 ServiceModel 跟踪日志中所有不需要的错误消息的来源都来自于连接池中的连接在服务器上超时,或者客户端进程退出时客户端断开连接。如果池化连接在服务器上超时,则会在超时时立即在服务器上写入一些跟踪消息,然后在客户端启动下一个操作时再次写入。这些是“错误”级别的跟踪消息。如果客户端进程在关闭连接之前退出,则在客户端进程退出时立即在服务器上获得不同的错误级别跟踪消息。这些是错误级别的跟踪消息,特别让人恼火,因为即使在生产环境中,我通常也会记录这些消息...但似乎这些大部分应该被忽略,因为它们是例行的连接池连接超时的结果。Microsoft 在此处解决了有关池化连接关闭问题的描述。

http://support.microsoft.com/kb/2607014

上文建议使用ServiceModel处理异常,如果在TraceLogs中看到它,则可以安全忽略。那种情况被记录为“信息”级别事件,这并不像我实际记录的“错误”级别事件一样困扰我。我试图从日志中“过滤”这些消息,但是这相当困难。
自然而然地,您可以通过在超时之前(在服务器上)显式关闭池化的连接(在客户端上)来完全避免出现这种情况。为了让客户端关闭连接池中的连接(对于带有tcp传输的WCF绑定),我知道的唯一有效方法是显式关闭ChannelFactory实例。实际上,如果您没有缓存这些实例(并且不使用通常为您缓存它们的ClientBase),那么您将不会有任何问题!如果您确实想要缓存您的ChannelFactory实例,则至少应在应用程序退出之前明确地关闭它们,这是我从未见过的建议。在客户端应用程序退出之前关闭它们将处理在服务器上作为ServiceModel“错误”跟踪记录的已删除套接字的主要来源之一。
以下是一个关闭通道工厂的小代码示例:
try
{
    if (channelFactory != null)
    {
        if (channelFactory.State != CommunicationState.Faulted)
        {
            channelFactory.Close();
        }
        else
        {
            channelFactory.Abort();
        }
    }
}
catch (CommunicationException)
{
    channelFactory.Abort();
}
catch (TimeoutException)
{
    channelFactory.Abort();
}
catch (Exception)
{
    channelFactory.Abort();
    throw;
}
finally
{
    channelFactory= null;
}

调用此代码的位置有点棘手。在内部,我在AppDomain.ProcessExit中安排它以“确保”它被调用,但同时建议我的服务基类的消费者在AppDomain.ProcessExit之前显式地调用“关闭缓存工厂”的代码,因为ProcessExit处理程序仅限于~3秒完成。当然,进程可能会突然关闭并永远不会调用它,但只要这种情况不经常发生以至于淹没服务器日志,就没问题。
至于池化连接超时...您可以将服务器上的TCP传输“连接池”超时值提高到非常高(几天)并在某些情况下可能会很好。这样至少使得服务器上的连接超时变得不太可能或不频繁发生。请注意,客户端的较短超时值似乎不会以任何方式影响该情况,因此该设置可以保持默认值。(理由:客户端的连接在下次需要连接时可能会被观察到已超时,但此时服务器将已超时并记录了错误,如果没有,那么客户端将关闭并创建新的连接并重新启动服务器超时期。但是,简单地使用连接也将重新启动服务器超时期。)

因此,无论客户端设置如何,您都必须在服务器上具有足够高的连接池超时时间,以覆盖客户端的不活动期。您可以通过减少客户端的池大小(maxOutboundConnectionsPerEndpoint)进一步减少池化连接超时的可能性,这样客户端就不会打开比实际需要更多的连接,留下未使用的连接并最终在服务器上超时。

对于内置绑定(如netTcpBinding),必须在代码中配置绑定的连接池。对于自定义绑定,您可以像这样在配置中进行配置(在此我将服务器设置为2天超时,并仅使用100个连接池):

  <customBinding>
    <binding name="tcp">
      <security authenticationMode="SecureConversation"/>
      <binaryMessageEncoding compressionFormat="GZip"/>
      <tcpTransport>
        <connectionPoolSettings idleTimeout="2.00:00:00"
                                maxOutboundConnectionsPerEndpoint="100" />
      </tcpTransport>
    </binding>
  </customBinding>

这两种方法(提高服务器端超时时间和在客户端退出时关闭ChannelFactory实例)可以解决您的问题,或者至少显著减少“可以忽略”的消息数量。确保连接池的服务器超时时间至少与客户端相同,以确保如果服务器超时(在ServiceModel中处理得更为优雅,具有较少的跟踪消息,并且正是上面链接的知识库文章所涉及的情况),则客户端将首先超时。

在服务器端,您理想情况下需要足够的maxOutboudnConnectionsPerEndpoint来服务于(客户端数量)x(它们的池化连接数量)。否则,您可能会在服务器上遇到池溢出,从而发出警告级别的跟踪事件。不太糟糕。如果服务器的池中没有可用连接,当新客户端尝试连接时,这会在客户端和服务器上生成一堆事件。在所有这些情况下(即使服务器上的池不断溢出),WCF都会恢复并正常工作,只是效率不高。至少在我的经验中是这样的...如果等待服务器连接池位置打开的“租赁时间”超时(默认为5分钟),那么它会完全失败吗?不确定...

一个最终的建议可能是定期关闭你的ChannelFactory对象并回收缓存副本。这对性能的影响可能很有限,假设客户端在ChannelFactory实例正在回收时不会尝试使用服务。例如,你可以安排缓存的ChannelFactory实例在创建后5分钟进行回收(而不是在最后一次使用后5分钟,因为它可能有多个池化连接中的一个长时间未被使用)。然后将服务器上的连接池超时设置为10分钟左右。但请确保服务器超时时间比ChannelFactory回收周期要长得多,因为当你去回收ChannelFactory时,你可能需要等待挂起的操作完成(同时,服务器上可能有一些未使用的池化连接已经超时)。
所有这些都是微小的优化,可能不值得做...但如果你在生产环境中记录Error级别的ServiceModel跟踪事件,你可能需要做一些事情(即使是禁用连接池或ChannelFactory缓存),否则你的日志可能会被“可以忽略”的错误淹没。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接