如何正确关闭客户端代理(现有连接被远程主机强行关闭)?

Posted

技术标签:

【中文标题】如何正确关闭客户端代理(现有连接被远程主机强行关闭)?【英文标题】:How to properly close a client proxy (An existing connection was forcibly closed by the remote host)? 【发布时间】:2014-01-12 00:32:56 【问题描述】:

在您将问题读到最后之前,请不要关闭重复;我已经用谷歌搜索了几个小时没有成功。


编辑:现在我确信它与 WCF 缓存打开的 TCP 连接(连接池)的方式有关。请查看问题末尾的编辑#5。


我基本上有一个使用netTcpBinding 配置的WCF 服务。即使我优雅地关闭客户端代理(参见下面的代码),服务器始终会记录“System.Net.Sockets.SocketException (0x80004005): An existing connection was forcibly closed by the remote host”。

我已将问题范围缩小到我可以编写的最基本的 WCF 示例。我在 WCF 跟踪生成的日志中遇到异常每次我关闭客户端应用程序。不过,我自己的代码中没有出现任何异常,这意味着它按预期工作,我无法调试任何东西来查看 WCF 在我的日志中添加错误时出了什么问题。

服务接口/实现:

[ServiceContract]
public interface IService1

    [OperationContract]
    string DoWork();

...
public class Service1 : IService1

    public string DoWork()
    
        return "12";
    

服务器端配置:

<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    <services>
      <service name="WebApplication1.Service1">
        <endpoint address="" binding="netTcpBinding" bindingConfiguration="netTcpEndpointBinding" contract="WebApplication1.IService1" />
      </service>
    </services>
    <bindings>
      <netTcpBinding>
        <binding name="netTcpEndpointBinding">
          <security mode="None" />
        </binding>
      </netTcpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

客户端配置:

<configuration>
  <system.serviceModel>
    <bindings>
      <netTcpBinding>
        <binding name="NetTcpBinding_IService1">
          <security mode="None" />
        </binding>
      </netTcpBinding>
    </bindings>
    <client>
      <endpoint address="net.tcp://localhost/WebApplication1/Service1.svc"
        binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IService1"
        contract="ServiceReference1.IService1" name="NetTcpBinding_IService1" />
    </client>
  </system.serviceModel>
</configuration>

使用服务的客户端代码(VS2012 使用“添加服务引用”为我生成客户端代理):

private async Task<string> TestTask()

    Service1Client proxy = null;

    try
    
        Console.WriteLine("Calling service");

        proxy = new Service1Client();
        return await proxy.DoWorkAsync();
    
    finally
    
        if (proxy.State != System.ServiceModel.CommunicationState.Faulted)
        
            Console.WriteLine("Closing client");
            proxy.Close();
        
        else
        
            Console.WriteLine("Aborting client");
            proxy.Abort();
        
    

一切正常:

呼叫服务

关闭客户端

12

但是一旦应用程序终止,服务器就会记录一个异常。我知道我不应该担心这个异常,因为它按预期工作(异常只出现在日志中)并且无论如何都可能发生,以防客户端在调用 .Close()/.Abort() 之前突然终止。

但是,这是正常行为吗?我的意思是,如果我正确关闭了我的客户端代理,我希望服务器记录异常(这会污染我的日志)。 我还假设在关闭客户端代理后,客户端和服务器之间仍然建立了一些 TCP 连接(未知状态),因为服务器仅在整个客户端应用程序终止后才记录异常。如果这样的连接仍然打开,这不会引入意外行为(例如最大连接客户端数)吗?这真的是预期的吗?

我发现了关于这个问题的不同线索:

http://social.msdn.microsoft.com/Forums/vstudio/en-US/0f548f9b-7051-46eb-a515-9185f504d605/error-using-nettcpbinding-an-existing-connection-was-forcibly-closed-by-the-remote-host?forum=wcf

wcf "An existing connection was forcibly closed by the remote host" after closing client

结论是“不在乎”。

有人可以通过一些参考资料确认并解释为什么会抛出此异常吗?

编辑:

异常的日志跟踪:

<Exception>
<ExceptionType>System.Net.Sockets.SocketException, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>
<Message>An existing connection was forcibly closed by the remote host</Message>
<StackTrace>
à System.ServiceModel.Channels.SocketConnection.HandleReceiveAsyncCompleted()
à System.ServiceModel.Channels.SocketConnection.OnReceiveAsync(Object sender, SocketAsyncEventArgs eventArgs)
à System.Net.Sockets.SocketAsyncEventArgs.FinishOperationAsyncFailure(SocketError socketError, Int32 bytesTransferred, SocketFlags flags)
à System.Net.Sockets.SocketAsyncEventArgs.CompletionPortCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
à System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
</StackTrace>
<ExceptionString>System.Net.Sockets.SocketException (0x80004005): An existing connection was forcibly closed by the remote host</ExceptionString>
<NativeErrorCode>2746</NativeErrorCode>
</Exception>

非常感谢

编辑 2:在 IIS 中托管服务或在 Windows 服务中自托管时,我遇到了同样的问题

编辑 3:这是重现问题的完整示例:http://speedy.sh/ENB59/wcf-test.zip

编辑 4:

我试图通过 WCF 为我建立的 TCP 连接来监控幕后实际发生的事情。

关闭客户端代理后,我仍然看到一个打开的 TCP 连接到我的服务器:

我认为这与要缓存的 TCP 连接以供将来重用(即连接池)有关,因为打开与服务器的新连接(在第一个客户端代理关闭后)不会创建新的 TCP 连接。如果我在我的应用程序中调用Console.WriteLine(new Test().TestTask().Result); 两次,我仍然只能看到一个打开的 TCP 连接。

我还注意到,如果我在关闭客户端通道后等待太久,此连接会因超时而终止。

编辑 5:好的,我在 MSDN 上找到了有关该连接池的文档:

NetTcpBinding 使用基于服务的 TCP 连接池 主机 DNS 名称和服务正在侦听的端口号。这 当客户端调用不同的服务时效果很好 不同的端口或服务托管在一个进程中并共享 一个港口。如果一个客户端调用多个服务共享一个端口 托管在不同的进程中,或者托管在 WAS/IIS 中,客户端 边池可能会导致与服务 A 的连接出现问题 为服务 B 重用,导致抛出异常, 连接中止,并创建了一个新通道。为了避免这个问题, 使用 CustomBinding 并指定不同的 ConnectionPoolSettings.GroupName 为客户端的每个服务 通信。

所以现在我的问题是:如果这是正常行为,我该怎么做才能防止我的日志因所有这些异常而受到污染?

【问题讨论】:

可能与控制台应用程序中的 async/await 有关吗?我记得在某处读到 async/await 由于线程问题不适合控制台应用程序。尝试在非异步方法中执行此操作,看看是否会出现相同的问题。 @Tim 我现在无法测试,但是是的,控制台应用程序和 GUI 应用程序之间存在区别:线程模型,即线程池与主 UI 线程。我不认为问题来自这里,因为它是我使用 WPF 构建的真实应用程序的简化示例。 还有一些想法。一,如果您还没有启用 WCF 跟踪。其次,我想知道finally 块是否可能是问题的根源。我只是把它放在那里,因为从合理的角度看,我没有看到您的代码有问题,但是如果在 try 块中发生了某些事情,您可能会尝试在频道上调用 Close()处于不良或故障状态。当然,您在 catch 块中将其设置为 null,但是如果发生了一些有趣的事情并且不是捕获的异常怎么办? @Tim 现在我今天再次阅读了我的问题,我想可能不是那么清楚。我实际上只在日志中看到这些异常(在激活 WCF 跟踪之后)。这意味着调试器根本不会抱怨异常。对于finally 块,是的,如果代理在await 之后和Close 之前由于任何原因进入故障状态,它肯定会抛出异常。 嗯....服务是否又调用了另一个服务?根据我自己的经验,我知道读取跟踪日志可能具有挑战性——日志中是否有任何迹象表明异常在通信流中的哪个位置被引发?是否有可能其他连接没有正确关闭并且这些连接出现故障或超时? 【参考方案1】:

要解决此错误,只需关闭 Channel Factory。

private async Task<string> TestTask()

    Service1Client proxy = null;

    try
    
        Console.WriteLine("Calling service");

        proxy = new Service1Client();
        return await proxy.DoWorkAsync();
    
    finally
    
        if (proxy.State != System.ServiceModel.CommunicationState.Faulted)
        
            Console.WriteLine("Closing client");
            proxy.ChannelFactory.Close();
            proxy.Close();
        
        else
        
            Console.WriteLine("Aborting client");
            proxy.Abort();
        
    

【讨论】:

这绝对有效。现在我需要找到一种方法来管理我的 channelFactory 的生命周期,因为我猜它应该被缓存一段时间并且关闭/重新打开它很耗时。这真的回答了这个问题,非常感谢。 @iamkrillin - 感谢您的修复! :) 要是谷歌早点给我这个就好了……要是微软能写出合适的软件就好了……要是……【参考方案2】:

编辑 好的。我能够使用您的代码重现该问题。好消息是我还偶然发现了一种复制它的方法。我认为您的代码没有任何问题。我认为错误正在被记录,因为您是在 VS 的调试器中运行它;并且当调试器关闭服务导致错误被记录时。

按照这些步骤,看看你是否不再收到错误(这对我来说每次都完美):

    右键单击服务项目并选择“调试”>“启动新实例” 右键单击控制台应用程序并选择“调试”>“启动新实例” 运行控制台以完成。 检查您的日志文件。 使用 WCF 测试客户端窗口而不是调试器停止按钮停止服务。 检查您的日志文件。

这是我执行上述步骤的冲击波视频链接:swf file

原始 您发布的代码在我的系统上运行良好。我在任何日志中都没有收到错误。顺便说一句,我会完全摆脱 catch 块,因为它除了重新抛出异常之外什么都不做。然后我会像下面这样写 finally 块。我认为它使代码更清晰,并向读者传达了如果抛出异常你什么都不做的想法。

Service1Client proxy = null;

try

    Console.WriteLine("Calling service");
    proxy = new Service1Client();
    return await proxy.DoWorkAsync();

finally

    if (proxy != null)
    
        if (proxy.State == CommunicationState.Faulted)
        
            Console.WriteLine("Aborting client");
            proxy.Abort();
        
        else
        
            Console.WriteLine("Closing client");
            proxy.Close();
        
    

【讨论】:

我发布的代码是对现实世界应用程序的简化(不是Exception,而是FaultException 以及其他例外情况);我正在更新问题以添加更简化的版本,以及重现问题的示例解决方案 我添加了一个带有示例解决方案的 zip 文件。还更新了问题并简化了代码(按照您的建议删除了 catch 异常) (1/2) 感谢您的帮助。我实际上无法重现您所描述的内容。在第 3 步,如果我关闭控制台应用程序,这实际上会在日志中产生异常(这在视频中很奇怪,因为在您的机器中,这一步也会更新日志,因为 VS 会提示重新加载文件)。实际上我不认为调试器是有原因的,因为它可能调试任何东西并且问题仍然存在(至少在我的开发机器和我的生产服务器上):按照步骤 1 和 2您提供,然后代替第 3 步 (2/2) 点击“Debug -> Detach all”让所有东西在没有任何调试器的情况下运行。然后关闭控制台应用程序,仍然会记录异常。在我的生产服务器中,所有内容都编译为“发布”并托管在 IIS 中,我可以看到同样的问题。 确实很奇怪。我可以可靠地重现该实验。我认为可以肯定的一件事是,这不是您的代码。肯定是框架中的某些东西导致了您的问题。【参考方案3】:

我怀疑您在 try 块中的 return 命令导致执行跳过 finally 块,导致您的连接保持打开状态,直到客户端关闭导致异常。这可能吗?你确定你的 finally 块被执行了吗?

【讨论】:

我确信finally 将始终被调用。我更新了问题以添加我在代码中添加的痕迹以显示它。 @ken2k 我明白了。好吧,您的 proxy.Close() 调用可能会失败(抛出异常)或服务器未正确处理,如下所述:relentlessdevelopment.wordpress.com/2010/01/17/… 这意味着之后连接仍保持打开状态。 可以,但这不是我的服务器日志中出现异常的原因。我提供的代码不会在客户端抛出任何异常。 @ken2k 不应该也用 await 调用 Close 还是我错了? 否; finally里面的代码会在异步调用完成后同步执行。

以上是关于如何正确关闭客户端代理(现有连接被远程主机强行关闭)?的主要内容,如果未能解决你的问题,请参考以下文章

EWS 连接被远程主机强行关闭

如何确定哪个 EndPoint 导致了错误代码 10054 的 SocketException? (“现有连接被远程主机强行关闭。”)

现有连接被远程主机强行关闭

访问 Photobucket API 时“现有连接被远程主机强行关闭”

现有连接被远程主机强行关闭

System.Net.Sockets.SocketException:'现有连接被远程主机强行关闭'