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在操作期间被创建和关闭,没有错误。

我的猜测是客户端保持打开套接字以供重用(明智的优化)。然后在应用程序退出时,该套接字被破坏。

这是否代表我应该修复的错误?如果它不是真正的“错误”,我可以以某种方式避免这种情况,以免将垃圾忽略在我的错误日志中吗?

客户端绑定配置与服务器完全相同(自然)。这是我的调用代码...注意我使用了这个问题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

4

1 回答 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 传输“ConnectionPool”超时值提高到非常高的值(几天),并且在某些情况下可能没问题。这至少会使服务器上的连接超时不太可能或不经常发生。请注意,在客户端上设置较短的超时值似乎不会以任何方式影响情况,因此该设置最好保留为默认值。(推理:下次客户端需要连接时,客户端的连接被观察为超时,但此时服务器要么已经超时并记录错误,否则,客户端将关闭并创建一个新的连接并重新启动服务器超时时间。

因此,无论客户端设置如何,您都必须在服务器上具有足够高的连接池超时时间,以覆盖客户端的不活动时间。您可以通过减小客户端上池的大小 (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 将恢复并运行,只是不是最佳状态。至少根据我的经验......如果新连接的“LeaseTime”超时等待服务器连接池点打开(默认为5分钟),那么它可能会完全失败?没有把握...

最后的建议可能是定期关闭您的 ChannelFactory 对象并回收缓存的副本。这可能对性能的影响有限,假设客户端在 ChannelFactory 实例回收时没有尝试完全使用服务。例如,您可以在缓存 ChannelFactory 实例创建后的 5 分钟内安排回收(而不是在它最后一次使用后,因为它可能有多个池连接,其中一个有一段时间未使用)。然后将服务器上的连接池超时设置为 10 分钟左右。但是请确保服务器超时时间在 ChannelFactory 回收期间是一个不错的时间,因为当您去回收 ChannelFactory 时,您可能必须等到待处理的操作完成(同时一些未使用的池连接可能刚刚在服务器上超时)。

所有这些都是微优化,可能不值得做......但是如果您在生产中记录错误级别的 ServiceModel 跟踪事件,您可能想要做一些事情(即使它正在禁用连接池或 ChannelFactory 缓存)或您的日志可能会被“安全忽略”的错误所淹没。

于 2014-05-02T23:22:13.333 回答