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

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


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


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

我已经将问题缩小到我能写的最基本的WCF示例。 我每次关闭客户端应用程序时都会在WCF跟踪生成的日志中获得exception。 我在自己的代码中没有得到任何exception,这意味着它按预期工作,我无法调试任何东西,看看WCF在我的日志中添加错误出了什么问题。

服务接口/实现:

 [ServiceContract] public interface IService1 { [OperationContract] string DoWork(); } ... public class Service1 : IService1 { public string DoWork() { return "12"; } } 

服务器端配置:

                              

客户端配置:

               

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

 private async Task 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

但是一旦应用程序终止,服务器就会记录一个exception。 我理解我不应该担心这个exception,因为它按预期工作(exception只出现在日志中),如果客户端在调用.Close() / .Abort()之前突然终止,可能会发生这种情况。

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

我找到了关于这个问题的不同主题:

http://social.msdn.microsoft.com/Forums/vstudio/en-US/0f548f9b-7051-46eb-a515-9185f504d605/error-using-nettcpbinding-an-existing-connection-was-forcibly-closed-by-在远程主机?论坛= WCF

wcf“关闭客户端后,远程主机强行关闭现有连接”

结论将是“不关心它”。

有人可以通过一些参考确认,并解释为什么抛出这个exception?

编辑:

记录exception的日志:

  System.Net.Sockets.SocketException, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 An existing connection was forcibly closed by the remote host  à 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)  System.Net.Sockets.SocketException (0x80004005): An existing connection was forcibly closed by the remote host 2746  

非常感谢

编辑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根据服务的主机DNS名称和服务正在侦听的端口号使用TCP连接池。 当客户端在不同端口上调用不同服务,或者服务在单个进程中托管并共享端口时,这种方法很有效。 如果单个客户端调用多个服务共享托管在不同进程中的端口,或托管WAS / IIS,则客户端池可能会导致与服务A的连接重新用于服务B的问题,从而导致抛出exception,连接中止,并创建了一个新频道。 要避免此问题,请使用CustomBinding并为客户端与之通信的每个服务指定不同的ConnectionPoolSettings.GroupName。

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

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

 private async Task 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(); } } } 

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

按照以下步骤操作,看看你是否不再收到错误(这对我来说每次都很合适):

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

这是我执行上述步骤的冲击波video的链接: swf文件

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

 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(); } } } 

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