使用WCF ChannelFactory和连接超时的最佳实践

7
我正在开发一个winform应用程序,它将访问作为Windows服务自托管的WCF服务。我使用ChannelFactory而不是service reference。我已成功连接并调用了WCF服务。问题在于当我让应用程序空闲20分钟,然后尝试再次调用时,会收到以下错误消息:“socket connection was aborted. This could be caused by an error processing your message or a receive timeout being exceeded by the remote host, or an underlying network resource issue. Local socket timeout was '00:00:59.9489970'.” 我正在寻找最佳实践来管理连接。我目前创建了一个名为PrepareWCFConnection的函数(如下),它检查通道和ChannelFactory的状态。在调用WCF服务之前,我会调用这个方法。有没有更好的处理方法?
     public bool PrepareWCFConnection()
    {
        if ((channelFactory == null) || 
            (channelFactory.State == CommunicationState.Faulted) ||
            (channelFactory.State != CommunicationState.Opened))
        {
            channelFactory = new ChannelFactory<IService1>(new NetTcpBinding(), endpointAddress);
        }


        if ((proxy == null) ||
            (((IClientChannel)proxy).State == CommunicationState.Faulted) ||
            (((IClientChannel)proxy).State != CommunicationState.Opened))
        {
            proxy = channelFactory.CreateChannel(endpointAddress);
            ((IClientChannel)proxy).Open();
        }

        return true;
    }

以上代码的更多测试证明它无法工作。ChannelFactory和channel都是打开的,但在让系统空闲一段时间后,我仍然会收到以下错误:套接字连接被中止。这可能是由于处理您的消息时出现错误或远程主机超过接收超时,或底层网络资源问题所致。本地套接字超时为“00:00:59.9479970”。 - econner
1
这是来自 MSDN 的链接,展示了如何创建 channelfactory 和 channel,进行调用并关闭 channel,然后关闭 channel factory。然而,如果您使用凭据进行身份验证,那么在每个方法调用后关闭 channel 并在每个方法之前重新创建 channel,会对资源和时间造成昂贵的开销吗?http://msdn.microsoft.com/en-us/library/ms734681.aspx - econner
1
经过更多的测试,我首先使用PrepareWCFConnection()调用WCF服务...然后调用我的服务方法,最后调用((IClientChannel)proxy).Close(); 这将关闭通道连接,然后为每个方法调用创建一个新通道。这是最佳实践吗? - econner
2
每次调用后,我都会关闭通道。如果保持通道打开但不活动,则在10分钟后会收到CommunicationFaultedException异常。如果您确实想保持通道打开,请查看ReliableService。不要关闭ChannelFactory,而是重复使用它,因为它执行所有初始配置。 - Michael
1
请看这个链接:http://www.codeproject.com/Tips/507328/Creating-and-closing-of-WCF-proxies - Michael
刚看到这个已经有一年了。 - Michael
3个回答

6
如果要重用现有通道,则需要通过每9分钟向服务发送ping来保持通道的活动状态。我认为默认的接收超时时间为10分钟,因此如果保持闲置超过这个时间,通道将断开连接。或者,您可以使用可靠会话来保持通道的活动状态。
如果不需要在同一通道上进行回调,则最好在完成后关闭通道并为每个服务操作重新创建新通道。创建通道并不昂贵。您可以缓存通道工厂,但为每个调用创建通道。

2
好的建议:您可以缓存通道工厂,但为每个调用创建新的通道。 - baraban

2
我知道这个问题现在已经很老了,但我看到它没有真正得到解答。当涉及到通道超时时,有两个超时时间(如果您不使用可靠的消息传递,则只有1个)需要关注。在服务端,如果在超时期限内未接收到任何应用程序消息,则会触发“ReceiveTimeout”。该超时的默认值为10分钟。
此外,如果启用了“ReliableSession”,则还要使用“InactivityTimeout”。该超时是通道允许另一个通信方在发送任何消息之前不发送任何消息的最长时间,然后使通道出现故障。
为了增加通道的生命周期,建议您启用“ReliableSession”,然后将“ReceiveTimeout”和“InactivityTimeout”都设置为更高的值。 ReliableSession通过发送ILM(基础结构级别的消息),如保持活动状态的信息(也会发送ACK),来使通道保持活动状态。如果在“InactivityTimeout”到期之前未收到保持活动状态的信息或ALM(应用程序级别的消息),则通道将出现故障。
此外,如果在“ReceiveTimeout”到期之前未收到ALM(应用程序级别的消息),则通道将出现故障。
因此,建议将两个超时时间都增加到相同的值,或将“ReceiveTimeout”设置为比“InactivityTimeout”更高的值。
顺便提一下,将“ReceiveTimeout”设置在客户端上将不起作用,它仅是服务端超时。但是,在服务端使用ReliableSession时,客户端也必须这样实现:
NetTcpBinding binding = new NetTcpBinding
        {
            ReliableSession = { Enabled = true },             
            SendTimeout = TimeSpan.FromMinutes( 1 )
        };

        binding.ReliableSession.InactivityTimeout = TimeSpan.Parse( "24.20:31:23.6470000" );

服务端的 app.config 文件应该长这样:

<bindings>
    <netTcpBinding>
      <binding name="netTestTcpBinding"
               receiveTimeout="24.20:31:23.6470000">
        <reliableSession inactivityTimeout="24.20:31:23.6470000"
                         enabled="true" />
      </binding>
    </netTcpBinding>
  </bindings>
<services>
  <service>
    <endpoint address="IServiceContract"
              binding="netTcpBinding"
              bindingConfiguration="netTestTcpBinding"
              name="serviceContractTcpBinding"/>
    <host>
        <baseAddresses>
             <add baseAddress="net.tcp://localhost:12001/" />
        </baseAddresses>
     </host>
    </service>                   
</services>

0

一个相当直接的解决方案是重用你的通道,而不需要轮询或做花哨的事情,只需关注对你的通道的最后一次调用,并检查wcfC.Binding.ReceiveTimeout以重新生成它(如果需要),类似于:

    TimeSpan timeSpan = DateTime.Now - LastCallTime;
    if (timeSpan.TotalSeconds > wcfC.Binding.ReceiveTimeout.TotalSeconds || wcfC.State != CommunicationState.Opened)
    { 
       wcfC.Abort();
       wcfC = new WCFChannel();

    }    
    LastCallTime = DateTime.Now;

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