如何正确关闭客户端代理(远程主机强制关闭了一个现有的连接)?

12

在您阅读完问题之前,请不要将其标记为重复;我已经谷歌了几个小时,但没有成功。


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


我基本上有一个使用netTcpBinding配置的WCF服务。即使我正常关闭客户端代理(见下面的代码),服务器仍然记录“System.Net.Sockets.SocketException (0x80004005): 远程主机强制关闭了现有的连接”

我将问题缩小到了我可以编写的最基本的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>

非常感谢

EDIT 2:当我在IIS中托管服务或者在Windows服务中进行自主托管时,我遇到了同样的问题。

EDIT 3: 这里是一个完整的示例以重现该问题: http://speedy.sh/ENB59/wcf-test.zip

EDIT 4:

我试图监视WCF为我建立的TCP连接底层实际发生的情况。

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

enter image description here

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

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

EDIT 5:好的,我在MSDN上找到了关于连接池的文档:

NetTcpBinding基于服务的主机DNS名称和服务侦听的端口号使用TCP连接池。当客户端对不同端口上的不同服务进行调用时,或者服务托管在单个进程中且共享一个端口时,这很有效。如果单个客户端调用在不同进程中托管的多个共享端口的服务,或者是WAS/IIS托管的服务,则客户端端的池化可能导致将Service A的连接重用于Service B,从而导致抛出异常,连接中止并创建新通道。要避免这个问题,请使用CustomBinding并为客户端与之通信的每个服务指定不同的ConnectionPoolSettings.GroupName。

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


这可能与您的控制台应用程序中的async/await有关吗?我记得在某个地方读到过,由于线程问题,async/await不适用于控制台应用程序。尝试使用非异步方法进行操作,看看是否出现相同的问题。 - Tim
@Tim 我现在无法进行测试,但是控制台应用程序和GUI应用程序之间是有区别的:线程模型,即线程池与主UI线程。不过我不认为问题来自于此,因为这只是我使用WPF构建的真实应用程序的简化示例。 - ken2k
另外还有几个想法。首先,如果您尚未启用WCF跟踪,请启用它。其次,我想知道finally块是否可能是问题的源头。我只是提出这个问题,因为在合理的审视下,我没有看到您代码中的问题,但是如果在try块中发生了什么事情,您可能会尝试在一个不良或故障状态的通道上调用Close()。尽管您在catch块中将其设置为null,但如果发生了一些有趣的事情,并且它不是捕获的异常呢? - Tim
好的,如果有机会,请将其添加到您的问题中;也许其他人会知道发生了什么。如果您想将此离线处理,可以给我发送电子邮件(我的个人资料中有地址),并在我们弄清楚答案后发布它。这可能没有什么可担心的,但我不喜欢把这样的事情留着因为它们有一个很讨厌的习惯,就是以后会回来咬你 :) - Tim
据我所知,通过在客户端调用proxy.Close();,您正在告诉服务器强制关闭连接,因此从逻辑上讲,服务器记录了正确的异常,或者我在这里漏掉了什么吗? - Furqan Hameedi
显示剩余3条评论
3个回答

9
为了解决这个错误,只需关闭 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生命周期的方法,因为我猜它应该被缓存一段时间,而关闭/重新打开它是耗时的。这真正回答了我的问题,非常感谢。 - ken2k
@iamkrillin - 謝謝你的修復! :) 如果 Google 能早點給我這個就好了... 如果微軟能夠寫出合適的軟體就好了... 如果... - Marko

2

编辑 好的,我使用你的代码成功复现了问题。好消息是,我也找到了一种方法复现它。我认为问题并不在于你的代码上。我认为记录错误是由于您在VS调试器中运行它时,当调试器关闭服务时会导致错误被记录。

按照以下步骤操作,看看是否不再出现错误(这对我来说每次都完美运作):

  1. 右键单击服务项目,选择调试 > 启动新实例
  2. 右键单击控制台应用程序,选择调试 > 启动新实例
  3. 运行控制台应用程序直至完成。
  4. 检查您的日志文件。
  5. 使用WCF测试客户端窗口停止服务,而不是调试器停止按钮。
  6. 检查您的日志文件。

这里有一个我执行以上步骤的Shockwave视频链接:swf文件

原文 你发布的代码在我的系统上运行良好,没有任何日志错误。顺便说一下,我会完全删除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以及其他异常);我正在更新问题,添加更简单的版本以及一个示例解决方案来复现该问题。 - ken2k
我添加了一个带有示例解决方案的压缩文件。同时更新了问题并简化了代码(按照您的建议删除了catch异常)。 - ken2k
谢谢您的帮助。实际上我无法重现您所描述的情况。在第三步,如果我关闭控制台应用程序,这实际上会在日志中产生异常(在视频中很奇怪,因为在您的机器上,此步骤也会更新日志,因为VS提示重新加载文件)。实际上,我认为调试器不是问题的原因,因为即使不调试任何内容,问题仍然存在(至少在我的开发机器和生产服务器上):按照您提供的第1步和第2步,然后执行第3步的替代方案。 - ken2k
点击“调试->分离所有”以让一切都在没有调试器的情况下运行。然后关闭控制台应用程序,异常仍然被记录。在我的生产服务器上,所有东西都编译为“发布”并托管在IIS中,我也能看到完全相同的问题。 - ken2k
我更新了问题,并提供了更多关于正在发生的事情的信息。 - ken2k
1
确实非常奇怪。我可以可靠地重现这个实验。我确定的一件事是,问题不在你的代码上。肯定是框架中的某些东西导致了你的问题。 - John Kraft

0

我怀疑你在 try 块内的 return 命令导致执行跳过 finally 块,从而使连接保持打开状态,直到客户端关闭引起异常。这种情况可能发生吗?你是否确保执行了 finally 块?


我相信 finally 总是会被调用。我更新了问题,添加了我在代码中放置的跟踪以展示它。 - ken2k
@ken2k 我明白了。嗯,你的 proxy.Close() 调用可能会失败(抛出异常),或者服务器没有正确处理它,正如这里所述:http://relentlessdevelopment.wordpress.com/2010/01/17/closing-a-wcf-client-the-proper-way/ 这意味着连接仍然保持打开状态。 - o_weisman
它可能会,但不是我的服务器日志中异常的原因。我提供的代码在客户端没有抛出任何异常。 - ken2k
@ken2k,Close 方法也应该使用 await 调用,不然会有问题吧? - o_weisman
我确定,因为我在问题中提供的完全相同的代码在客户端不会抛出任何异常(通过附加调试器很容易验证),但是会记录服务器端的异常。 - ken2k
显示剩余2条评论

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