通过WCF进行文件下载比通过IIS慢

时间:2009-01-16 18:02:01

标签: wcf streaming

以下方法(我希望在为这篇文章大大简化它时没有犯任何错误)正常工作并使用net.tcp协议上的流传输模式进行设置。问题是性能明显差于通过IIS通过http下载相同的文件。为什么会这样,我可以改变什么来改善性能呢?

Stream WebSiteStreamedServiceContract.DownloadFile( string filePath ) {
    return File.OpenRead( filePath );
}

最后,WCF是否负责妥善处理我的流,并且它是否做得很好?如果没有,我应该做什么呢?

谢谢。

6 个答案:

答案 0 :(得分:31)

经过8个月的处理这个问题,其中3个与微软,这里是解决方案。简短的回答是服务器端(发送大文件的一方)需要使用以下内容进行绑定:

 <customBinding>
  <binding name="custom_tcp">
   <binaryMessageEncoding />
   <tcpTransport connectionBufferSize="256192" maxOutputDelay="00:00:30" transferMode="Streamed">
   </tcpTransport>
  </binding>
 </customBinding>

这里的关键是connectionBufferSize属性。可能需要设置其他几个属性(maxReceivedMessageSize等),但connectionBufferSize是罪魁祸首。

无需在服务器端更改代码。

无需在客户端更改代码。

无需在客户端更改配置。

这是一个很长的答案:

我一直怀疑net.tcp对WCF的影响很慢是因为它经常发送小块信息而不是更频繁地发送大量信息,这使得它在高延迟网络上表现不佳(互联网)。事实证明这是真的,但到达那里是一条漫长的道路。

netTcpBinding上有几个属性听起来很有希望:maxBufferSize是最明显的,而maxBytesPerRead和其他人听起来也很有希望。除此之外,还可以创建比原始问题更复杂的流 - 您也可以在客户端和服务器端指定缓冲区大小。问题是这些都没有任何影响。一旦你使用了netTcpBinding,你就会受到冲击。

原因是在netTcpBinding上调整maxBufferSize会调整协议层上的缓冲区。但是你无法对netTcpBinding做任何调整底层传输层。这就是为什么我们失败了这么久才能取得进展。

自定义绑定解决了这个问题,因为增加传输层上的connectionBufferSize会增加一次发送的信息量,因此传输更不容易受到延迟的影响。

在解决这个问题时,我注意到maxBufferSize和maxBytesPerRead确实对低延迟网络(和本地)产生了性能影响。 Microsoft告诉我,maxBufferSize和connectionBufferSize是独立的,并且它们的值的所有组合(彼此相等,maxBufferSize大于connectionBufferSize,maxBufferSize小于connectionBufferSize)是有效的。我们成功使用了65536字节的maxBufferSize和maxBytesPerRead。但是,这对高延迟网络性能(原始问题)的影响非常小。

如果您想知道maxOutputDelay的用途,那么它是在框架抛出IO异常之前分配用于填充连接缓冲区的时间量。因为我们增加了缓冲区大小,所以我们还增加了分配给缓冲区的时间。

使用此解决方案,我们的性能提高了约400%,现在略好于IIS。还有其他几个因素影响IIS上的HTTP和WCF over net.tcp(以及WCF over http)的相对和绝对性能,但这是我们的经验。

答案 1 :(得分:3)

我不知道你的第一个问题的答案(我认为你需要提供更多代码来显示你正在为你的两个测试做什么),但是你关于流的处理的第二个问题,答案是你需要自己做。

Here is an excellent blog entry为此目的提供了一些很棒的代码。

答案 2 :(得分:2)

我使用反射在ConnectionBufferSize上使用@ Greg-Smalter建议和更改NetTcpBinding,这解决了我的问题。流媒体大文件现在快速尖叫。这是代码。

        var transport = binding.GetType().GetField("transport", BindingFlags.NonPublic | BindingFlags.Instance)
            ?.GetValue(binding);

        transport?.GetType().GetProperty("ConnectionBufferSize", BindingFlags.Public | BindingFlags.Instance)?.SetValue(transport, 256192);

答案 3 :(得分:0)

这是另一种方法,无需处理反射。只需将NetTcpBinding打包到CustomBinding

var binding = new CustomBinding(new NetTcpBinding
{
     MaxReceivedMessageSize = 2147483647,
     MaxBufferSize = 2147483647,
     MaxBufferPoolSize = 2147483647,
     ReceiveTimeout = new TimeSpan(4, 1, 0),
     OpenTimeout = new TimeSpan(4, 1, 0),
     SendTimeout = new TimeSpan(4, 1, 0),
     CloseTimeout = new TimeSpan(4, 1, 0),
     ReaderQuotas = XmlDictionaryReaderQuotas.Max,
     Security =
            {
                Mode = SecurityMode.None,
                Transport = {ClientCredentialType = TcpClientCredentialType.None}
            },
            TransferMode = TransferMode.Streamed,
            HostNameComparisonMode = HostNameComparisonMode.StrongWildcard
        });

binding.Elements.Find<TcpTransportBindingElement>().ConnectionBufferSize = 665600;

答案 4 :(得分:0)

以上基于反思的答案对我们有用。如果您需要通过托管的IIS / WCF服务执行此操作;您可以使用魔术配置函数声明来访问Binding来执行此操作:

public static void Configure(ServiceConfiguration config)  
{  
    NetTcpBinding tcpBinding = new NetTcpBinding { /* Configure here */ };

    var transport = tcpBinding.GetType().GetField("transport", BindingFlags.NonPublic | BindingFlags.Instance)
            ?.GetValue(tcpBinding);
    transport?.GetType().GetProperty("ConnectionBufferSize", BindingFlags.Public | BindingFlags.Instance)?.SetValue(transport, 256192);

    ServiceEndpoint se = new ServiceEndpoint(ContractDescription.GetContract(typeof(IService)), tcpBinding , new EndpointAddress("net.tcp://uri.foo/bar.svc"))
    {
        ListenUri = new Uri("net.tcp://uri.foo/bar.svc")
    };

    config.AddServiceEndpoint(se);

    config.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });  
    config.Description.Behaviors.Add(new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true });  
}  

答案 5 :(得分:0)

感谢您提供这篇文章中的信息。关于此问题的信息不多。我想添加一些可能对其他人有用的其他细节。

这里的大多数答复似乎都表明人们正在使用单个NetTcp端点,或者他们没有在IIS中托管WCF。

如果您在同一个wcf服务中使用多个netTcp终结点,并将其托管在IIS中,或者使用的是使用WAS的容器,则可能会遇到这些问题。

  1. 如果您更改一个NetTcp端点的ConnectionBufferSize,则必须全部更改它们,并且它们必须具有相同的值。显然,这是在WAS中托管的要求(这是IIS使用的)。 (https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-host-a-wcf-service-in-was)如果您是自托管Web服务,显然这不是问题,但我尚未对此进行验证。因此,如果您为一个NetTcp添加了customBinding,但是却遇到丢失TransportManager的激活异常,这就是您的问题。
  2. 我无法使用神奇的Configure方法来使它正常工作。即使基本的N​​etTcp绑定没有太多更改,我仍然得到了一个空响应对象。我敢肯定有办法解决这个问题,但是我没有时间继续研究这个问题。
  3. 对我有用的方法是使用customBinding。如果您通过Windows身份验证使用传输安全性,则只需添加<windowsStreamSecurity /> 因此绑定最终看起来像这样:
<binding name="CustomTcpBinding_ServerModelStreamed">
     <windowsStreamSecurity />
     <binaryMessageEncoding />
     <tcpTransport connectionBufferSize="5242880" maxReceivedMessageSize="2147483647" maxBufferSize ="2147483647" transferMode="Streamed" />
</binding>

您不必更改客户端NetTcp配置,这当然是因为您没有使用此处未反映的任何其他功能。对于customBinding,各节的顺序很重要。 https://docs.microsoft.com/en-us/dotnet/framework/wcf/extending/custom-bindings