11

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

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

最后,WCF 是否负责正确处理我的流,它在这方面做得好吗?如果没有,我应该怎么做?

谢谢你。

4

6 回答 6

31

在解决此问题 8 个月后,其中 3 个月与 Microsoft 合作,这是解决方案。简短的回答是服务器端(发送大文件的端)需要使用以下内容进行绑定:

 <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 over HTTP 和 WCF over net.tcp(以及 WCF over http,就此而言)的相对和绝对性能,但这是我们的经验。

于 2009-09-08T23:52:13.297 回答
3

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

这是一个出色的博客条目,其中包含一些用于此目的的出色代码。

于 2009-01-17T16:49:32.943 回答
2

我接受了@Greg-SmalterConnectionBufferSize关于NetTcpBinding使用反射的建议和更改,这解决了我的问题。流式传输大型文档现在正在快速尖叫。这是代码。

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

        transport?.GetType().GetProperty("ConnectionBufferSize", BindingFlags.Public | BindingFlags.Instance)?.SetValue(transport, 256192);
于 2017-09-07T15:26:02.587 回答
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;
于 2017-11-28T02:43:34.137 回答
0

上述基于反射的答案确实对我们有用。如果您需要通过托管的 IIS/WCF 服务执行此操作;您可以使用神奇的配置函数声明来访问绑定来执行此操作:

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 });  
}  
于 2018-04-26T14:11:11.353 回答
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)如果您是自我,显然这不是问题- 托管网络服务,但我还没有验证这一点。因此,如果您为一个 NetTcp 添加了 customBinding,但您收到有关缺少 TransportManager 的激活异常,这就是您的问题。
  2. 我无法使用神奇的配置方法来实现这一点。即使基本的 NetTcp 绑定没有太大变化,我也得到了一个空响应对象。我确信有办法解决这个问题,但我没有时间继续深入研究这个问题。
  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

于 2019-04-19T17:39:46.887 回答