流式传输大文件的 WCF 超时异常

WCF Timeout exception on streaming big files

我目前正在开发 WCF 流媒体服务。到目前为止,一切都适用于最大 2 GB 的文件。我已将该服务设置为流媒体服务,并且我自己将文件分块为 5 MB 块。但是,大于 2 GB 的文件(某处存在阈值)我总是会收到 InvalidOperationException 消息 Timeouts are not supported on this stream. 我不太确定为什么以及在何处抛出此异常。它认为这不是服务器端问题,因为每个请求都应该相同,而且大多数都可以工作。但异常来自生成的代理。所以来源是System.Private.ServiceModel

堆栈跟踪:

at System.Runtime.AsyncResult.End[TAsyncResult](IAsyncResult result)
   at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.End(SendAsyncResult result)
   at System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result)
   at System.ServiceModel.Channels.ServiceChannelProxy.TaskCreator.<>c__DisplayClass0.<CreateGenericTask>b__1(IAsyncResult asyncResult)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Company.OurApp.App.DataService.BaseFile.<DownloadItem>d__59.MoveNext()

这是我的服务器实现:

var response = new GetFileResponse();
                using (var impersonation = new Impersonation(request.Domain, request.Username, request.Password))
                {
                    using (Stream fStream = File.OpenRead(request.FullFilePath))
                    {
                        fStream.Seek(request.FilePart * request.FilePartSize, SeekOrigin.Begin);
                        BinaryReader bStream = new BinaryReader(fStream);
                        var filePart = bStream.ReadBytes(request.FilePartSize);

                        using (Stream mStream = new MemoryStream(filePart))
                        {
                            response.FileByteStream = mStream;
                            return response;
                        }
                    }
                }

GetFileResponse 如下所示:

[MessageContract]
public class GetFileResponse
{
    [MessageBodyMember(Order = 1)]
    public Stream FileByteStream { get; set; }
}

这是客户端处理下载(UWP App)的方式:

using (Stream f = await StorageFile.OpenStreamForWriteAsync())
                {
                    //Cancelation area - after every async operation if possilble
                    for (int i = 0; i < sections; i++)
                    {
                        token.ThrowIfCancellationRequested();
                        var response = await client.GetFilePartAsync(request.ConnectionPassword, request.Domain, i, FilePartSize, FullPath, request.Password, request.Username);
                        token.ThrowIfCancellationRequested();
                        DownloadProgress = response.FileByteStream.Length;

                        f.Seek(0, SeekOrigin.End);
                        await f.WriteAsync(response.FileByteStream, 0, response.FileByteStream.Length);
                        await f.FlushAsync();
                    }
                }

这是服务 web.config:

 <system.serviceModel>
    <services>
      <service behaviorConfiguration="HttpsServiceBehaviour"
               name="Company.OurApp.TransportService.DataService">
        <endpoint address="" binding="basicHttpBinding" bindingConfiguration="streamedBinding" contract="Company.OurAppTransportService.IDataService">
        </endpoint>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="HttpsServiceBehaviour">
          <!-- To avoid disclosing metadata information, set the values below to false before deployment -->
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <basicHttpBinding>
        <binding name="streamedBinding" transferMode="Streamed" closeTimeout="10:00:00">
          <security mode="TransportCredentialOnly">
            <transport clientCredentialType="Windows" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>

生成客户端代理时,我设置了一些超时,但这并没有改变任何东西:

public DataServiceClient GetDataServiceClient(string endpoint = null)
        {
            var useEndpoint = String.IsNullOrEmpty(endpoint) ? Configuration.ConfigService : endpoint;

            System.ServiceModel.BasicHttpBinding result = new System.ServiceModel.BasicHttpBinding();
            result.MaxBufferSize = int.MaxValue;
            result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
            result.MaxReceivedMessageSize = int.MaxValue;
            result.AllowCookies = true;
            result.Security.Transport.ClientCredentialType = System.ServiceModel.HttpClientCredentialType.Windows;

            //TODO Try to work with timeouts for larges files?
            result.SendTimeout = TimeSpan.FromMinutes(5);
            result.ReceiveTimeout = TimeSpan.FromMinutes(5);
            result.OpenTimeout = TimeSpan.MaxValue;


            if (useEndpoint.ToLower().StartsWith("https://"))
                result.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.Transport;
            else
                result.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.TransportCredentialOnly;

            var client = new DataServiceClient(result, new System.ServiceModel.EndpointAddress(String.Concat(useEndpoint, fixedEndpointSuffix)));
            client.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;

            if (AppState.IsLoggedIn)
            {
                client.ClientCredentials.Windows.ClientCredential.UserName = $@"{AppState.Domain}\{AppState.User}";
                client.ClientCredentials.Windows.ClientCredential.Password = AppState.Password;
            }

            return client;
        }

知道抛出异常的位置和原因吗?服务器?客户?它来自流吗?非常感谢帮助。

对于面临同样问题的其他人。我通过使用 WCF TraceViewer 分析异常来解决它。我还从控制台应用程序调用服务以确保它不是 UWP 问题。问题是我在响应到达客户端之前关闭了流。

错误的实现:

var response = new GetFileResponse();
            using (var impersonation = new Impersonation(request.Domain, request.Username, request.Password))
            {
                using (Stream fStream = File.OpenRead(request.FullFilePath))
                {
                    fStream.Seek(request.FilePart * request.FilePartSize, SeekOrigin.Begin);
                    BinaryReader bStream = new BinaryReader(fStream);
                    var filePart = bStream.ReadBytes(request.FilePartSize);

                    using (Stream mStream = new MemoryStream(filePart))
                    {
                        response.FileByteStream = mStream;
                        return response;
                    }
                }
            }

这个帮我修好了:

Stream fStream = File.OpenRead(request.FullFilePath);

                long offset = request.FilePart * request.FilePartSize;
                fStream.Seek(offset, SeekOrigin.Begin);

                BinaryReader bStream = new BinaryReader(fStream);
                var filePart = bStream.ReadBytes((int)request.FilePartSize);

                Stream mStream = new MemoryStream(filePart);

                response.FileByteStream = mStream;
                return response;

希望对您有所帮助!