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

问题描述 投票:10回答:6

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

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

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

谢谢。

wcf streaming
6个回答
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 over 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)的相对和绝对性能,但这是我们的经验。


3
投票

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

Here is an excellent blog entry有一些很好的代码就是为了这个目的。


2
投票

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

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

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

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;

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

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绑定没有太多变化,我也得到了一个null响应对象。我确定有办法解决这个问题,但我没有时间继续深入研究这个问题。
  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

© www.soinside.com 2019 - 2024. All rights reserved.