久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

【.NET】WebAPi之?dāng)帱c續(xù)傳下載(中)

 _明心見性_ 2019-07-19

前言

前情回顧:上一篇我們遺留了兩個問題,一個是未完全實現(xiàn)斷點續(xù)傳,另外則是在響應(yīng)時是返回StreamContent還是PushStreamContent呢,?這一節(jié)我們重點來解決這兩個問題,,同時就在此過程中需要注意的地方一并指出,,若有錯誤之處,,請指出,。

StreamContent compare to PushStreamContent

我們來看看StreamContent代碼,,如下:

復(fù)制代碼

  1. public class StreamContent : HttpContent
  2. {
  3. // Fields
  4. private int bufferSize;
  5. private Stream content;
  6. private bool contentConsumed;
  7. private const int defaultBufferSize = 0x1000;
  8. private long start;
  9. // Methods
  10. public StreamContent(Stream content);
  11. ]
  12. public StreamContent(Stream content, int bufferSize);
  13. protected override Task<Stream> CreateContentReadStreamAsync();
  14. protected override void Dispose(bool disposing);
  15. private void PrepareContent();
  16. protected override Task SerializeToStreamAsync(Stream stream, TransportContext context);
  17. protected internal override bool TryComputeLength(out long length);
  18. // Nested Types
  19. private class ReadOnlyStream : DelegatingStream
  20. {......}
  21. }

復(fù)制代碼

似乎沒有什么可看的,,但是有一句話我們需要注意,如下:

 private const int defaultBufferSize = 0x1000;

在StreamContent的第二個構(gòu)造函數(shù)為

 public StreamContent(Stream content, int bufferSize);

上述給定的默認一次性輸入到緩沖區(qū)大小為4k,,這對我們有何意義呢,?當(dāng)我們寫入到響應(yīng)中時,一般我們直接利用的是第一個構(gòu)造函數(shù),,如下:

  1. var response = new HttpResponseMessage();
  2. response.Content = new StreamContent(fileStream);

到這里我們明白了這么做是有問題的,,當(dāng)下載時默認讀取的是4k,如果文件比較大下載的時間則有延長,,所以我們在返回時一定要給定緩沖大小,,那么給定多少呢?為達到更好的性能最多是80k,如下:

  1. private const int BufferSize = 80 * 1024;
  2. response.Content = new StreamContent(fileStream, BufferSize);

此時下載的速度則有很大的改善,,有人就說了為何是80k呢,?這個問題我也不知道,老外驗證過的,,這是鏈接【.NET Asynchronous stream read/write】,。

好了說完StreamContent,接下來我們來看看PushStreamContent,,從字面意思來為推送流內(nèi)容,,難道是充分利用了緩沖區(qū)嗎,猜測可以有,,就怕沒有任何想法,我們用源碼來證明看看,。

我們只需看看WebHost模式下對于緩沖策略是怎么選擇的,,我們看看此類 WebHostBufferPolicySelector  實現(xiàn),代碼如下:

復(fù)制代碼

  1. /// <summary>
  2. /// Provides an implementation of <see cref="IHostBufferPolicySelector"/> suited for use
  3. /// in an ASP.NET environment which provides direct support for input and output buffering.
  4. /// </summary>
  5. public class WebHostBufferPolicySelector : IHostBufferPolicySelector
  6. {
  7. ....../// <summary>
  8. /// Determines whether the host should buffer the <see cref="HttpResponseMessage"/> entity body.
  9. /// </summary>
  10. /// <param name="response">The <see cref="HttpResponseMessage"/>response for which to determine
  11. /// whether host output buffering should be used for the response entity body.</param>
  12. /// <returns><c>true</c> if buffering should be used; otherwise a streamed response should be used.</returns>
  13. public virtual bool UseBufferedOutputStream(HttpResponseMessage response)
  14. {
  15. if (response == null)
  16. {
  17. throw Error.ArgumentNull("response");
  18. }
  19. // Any HttpContent that knows its length is presumably already buffered internally.
  20. HttpContent content = response.Content;
  21. if (content != null)
  22. {
  23. long? contentLength = content.Headers.ContentLength;
  24. if (contentLength.HasValue && contentLength.Value >= 0)
  25. {
  26. return false;
  27. }
  28. // Content length is null or -1 (meaning not known).
  29. // Buffer any HttpContent except StreamContent and PushStreamContent
  30. return !(content is StreamContent || content is PushStreamContent);
  31. }
  32. return false;
  33. }
  34. }

復(fù)制代碼

從上述如下一句可以很明顯的知道:

 return !(content is StreamContent || content is PushStreamContent);

除了StreamContent和PushStreamContent的HttpContent之外,,其余都進行緩沖,,所以二者的區(qū)別不在于緩沖,那到底是什么呢,?好了我們還未查看PushStreamContent的源碼,,我們繼續(xù)往下走,查看其源代碼如下,,我們僅僅只看關(guān)于這個類的描述以及第一個構(gòu)造函數(shù)即可,,如下:

復(fù)制代碼

  1. /// <summary>
  2. /// Provides an <see cref="HttpContent"/> implementation that exposes an output <see cref="Stream"/>
  3. /// which can be written to directly. The ability to push data to the output stream differs from the
  4. /// <see cref="StreamContent"/> where data is pulled and not pushed.
  5. /// </summary>
  6. public class PushStreamContent : HttpContent
  7. {
  8. private readonly Func<Stream, HttpContent, TransportContext, Task> _onStreamAvailable;
  9. /// <summary>
  10. /// Initializes a new instance of the <see cref="PushStreamContent"/> class. The
  11. /// <paramref name="onStreamAvailable"/> action is called when an output stream
  12. /// has become available allowing the action to write to it directly. When the
  13. /// stream is closed, it will signal to the content that is has completed and the
  14. /// HTTP request or response will be completed.
  15. /// </summary>
  16. /// <param name="onStreamAvailable">The action to call when an output stream is available.</param>
  17. public PushStreamContent(Action<Stream, HttpContent, TransportContext> onStreamAvailable)
  18. : this(Taskify(onStreamAvailable), (MediaTypeHeaderValue)null)
  19. {
  20. }
  21. ......
  22. }

復(fù)制代碼

對于此類的描述大意是:PushStreamContent與StreamContent的不同在于,PushStreamContent在于將數(shù)據(jù)push【推送】到輸出流中,,而StreamContent則是將數(shù)據(jù)從流中【拉取】,。 

貌似有點晦澀,我們來舉個例子,,在webapi中我們常常這樣做,,讀取文件流并返回到響應(yīng)流中,若是StreamContent,,我們會如下這樣做:

response.Content = new StreamContent(File.OpenRead(filePath));

上面的釋義我用大括號著重括起,,StreamContent著重于【拉取】,當(dāng)響應(yīng)時此時將從文件流寫到輸出流,,通俗一點說則是我們需要從文件流中去獲取數(shù)據(jù)并寫入到輸出流中,。我們再來看看PushStreamContent的用法,如下:

復(fù)制代碼

  1. XDocument xDoc = XDocument.Load("cnblogs_backup.xml", LoadOptions.None);
  2. PushStreamContent xDocContent = new PushStreamContent(
  3. (stream, content, context) =>
  4. {
  5. xDoc.Save(stream);
  6. stream.Close();
  7. },
  8. "application/xml");

復(fù)制代碼

PushStreamContent著重于【推送】,,當(dāng)我們加載xml文件時,,當(dāng)我們一旦進行保存時此時則會將數(shù)據(jù)推送到輸出流中。

二者區(qū)別在于:StreamContent從流中【拉取】數(shù)據(jù),而PushStreamContent則是將數(shù)據(jù)【推送】到流中,。

那么此二者應(yīng)用的場景是什么呢,?

(1)對于下載文件我們則可以通過StreamContent來實現(xiàn)直接從流中拉取,若下載視頻流此時則應(yīng)該利用PushStreamContent來實現(xiàn),,因為未知服務(wù)器視頻資源的長度,,此視頻資源來源于別的地方。

(2)數(shù)據(jù)量巨大,,發(fā)送請求到webapi時利用PushStreamContent,。

當(dāng)發(fā)送請求時,常常序列化數(shù)據(jù)并請求webapi,,我們可能這樣做:

  1. var client = new HttpClient();
  2. string json = JsonConvert.SerializeObject(data);
  3. var response = await client.PostAsync(uri, new StringContent(json));

當(dāng)數(shù)據(jù)量比較小時沒問題,,若數(shù)據(jù)比較大時進行序列化此時則將序列化的字符串加載到內(nèi)存中,鑒于此這么做不可行,,此時我們應(yīng)該利用PushStreamContent來實現(xiàn),。

復(fù)制代碼

  1. var client = new HttpClient();
  2. var content = new PushStreamContent((stream, httpContent, transportContext) =>
  3. {
  4. var serializer = new JsonSerializer();
  5. using (var writer = new StreamWriter(stream))
  6. {
  7. serializer.Serialize(writer, data);
  8. }
  9. });
  10. var response = await client.PostAsync(uri, content);

復(fù)制代碼

為什么要這樣做呢?我們再來看看源碼,,里面存在這樣一個方法,。

  protected override Task SerializeToStreamAsync(Stream stream, TransportContext context);

其內(nèi)部實現(xiàn)利用異步狀態(tài)機實現(xiàn),所以當(dāng)數(shù)據(jù)量巨大時利用PushStreamContent來返回將會有很大的改善,,至此,,關(guān)于二者的區(qū)別以及常見的應(yīng)用場景已經(jīng)敘述完畢,接下來我們繼續(xù)斷點續(xù)傳問題,。

斷點續(xù)傳改進 

上一篇我們講過獲取Range屬性中的集合通過如下:

request.Headers.Range

我們只取該集合中的第一個范圍元素,,通過如下

 RangeItemHeaderValue range = rangeHeader.Ranges.First();

此時我們忽略了返回的該范圍對象中有當(dāng)前下載的進度

  1. range.From.HasValue
  2. range.To.HasValue

我們獲取二者的值然后進行重寫Stream實時讀取剩余部分,下面我們一步一步來看,。

定義文件操作接口

復(fù)制代碼

  1. public interface IFileProvider
  2. {
  3. bool Exists(string name);
  4. FileStream Open(string name);
  5. long GetLength(string name);
  6. }

復(fù)制代碼

實現(xiàn)該操作文件接口

復(fù)制代碼

  1. public class FileProvider : IFileProvider
  2. {
  3. private readonly string _filesDirectory;
  4. private const string AppSettingsKey = "DownloadDir";
  5. public FileProvider()
  6. {
  7. var fileLocation = ConfigurationManager.AppSettings[AppSettingsKey];
  8. if (!String.IsNullOrWhiteSpace(fileLocation))
  9. {
  10. _filesDirectory = fileLocation;
  11. }
  12. }
  13. /// <summary>
  14. /// 判斷文件是否存在
  15. /// </summary>
  16. /// <param name="name"></param>
  17. /// <returns></returns>
  18. public bool Exists(string name)
  19. {
  20. string file = Directory.GetFiles(_filesDirectory, name, SearchOption.TopDirectoryOnly)
  21. .FirstOrDefault();
  22. return true;
  23. }
  24. /// <summary>
  25. /// 打開文件
  26. /// </summary>
  27. /// <param name="name"></param>
  28. /// <returns></returns>
  29. public FileStream Open(string name)
  30. {
  31. var fullFilePath = Path.Combine(_filesDirectory, name);
  32. return File.Open(fullFilePath,
  33. FileMode.Open, FileAccess.Read, FileShare.Read);
  34. }
  35. /// <summary>
  36. /// 獲取文件長度
  37. /// </summary>
  38. /// <param name="name"></param>
  39. /// <returns></returns>
  40. public long GetLength(string name)
  41. {
  42. var fullFilePath = Path.Combine(_filesDirectory, name);
  43. return new FileInfo(fullFilePath).Length;
  44. }
  45. }

復(fù)制代碼

獲取范圍對象中的值進行賦值給封裝的對象

復(fù)制代碼

  1. public class FileInfo
  2. {
  3. public long From;
  4. public long To;
  5. public bool IsPartial;
  6. public long Length;
  7. }

復(fù)制代碼

下載控制器,,對文件操作進行初始化

復(fù)制代碼

  1. public class FileDownloadController : ApiController
  2. {
  3. private const int BufferSize = 80 * 1024;
  4. private const string MimeType = "application/octet-stream";
  5. public IFileProvider FileProvider { get; set; }
  6. public FileDownloadController()
  7. {
  8. FileProvider = new FileProvider();
  9. }
  10. ......
  11. }

復(fù)制代碼

接下來則是文件下載的邏輯,首先判斷請求文件是否存在,,然后獲取文件的長度

  1. if (!FileProvider.Exists(fileName))
  2. {
  3. throw new HttpResponseException(HttpStatusCode.NotFound);
  4. }
  5. long fileLength = FileProvider.GetLength(fileName);

將請求中的范圍對象From和To的值并判斷當(dāng)前已經(jīng)下載進度以及剩余進度

復(fù)制代碼

  1. private FileInfo GetFileInfoFromRequest(HttpRequestMessage request, long entityLength)
  2. {
  3. var fileInfo = new FileInfo
  4. {
  5. From = 0,
  6. To = entityLength - 1,
  7. IsPartial = false,
  8. Length = entityLength
  9. };
  10. var rangeHeader = request.Headers.Range;
  11. if (rangeHeader != null && rangeHeader.Ranges.Count != 0)
  12. {
  13. if (rangeHeader.Ranges.Count > 1)
  14. {
  15. throw new HttpResponseException(HttpStatusCode.RequestedRangeNotSatisfiable);
  16. }
  17. RangeItemHeaderValue range = rangeHeader.Ranges.First();
  18. if (range.From.HasValue && range.From < 0 || range.To.HasValue && range.To > entityLength - 1)
  19. {
  20. throw new HttpResponseException(HttpStatusCode.RequestedRangeNotSatisfiable);
  21. }
  22. fileInfo.From = range.From ?? 0;
  23. fileInfo.To = range.To ?? entityLength - 1;
  24. fileInfo.IsPartial = true;
  25. fileInfo.Length = entityLength;
  26. if (range.From.HasValue && range.To.HasValue)
  27. {
  28. fileInfo.Length = range.To.Value - range.From.Value + 1;
  29. }
  30. else if (range.From.HasValue)
  31. {
  32. fileInfo.Length = entityLength - range.From.Value + 1;
  33. }
  34. else if (range.To.HasValue)
  35. {
  36. fileInfo.Length = range.To.Value + 1;
  37. }
  38. }
  39. return fileInfo;
  40. }

復(fù)制代碼

在響應(yīng)頭信息中的對象ContentRangeHeaderValue設(shè)置當(dāng)前下載進度以及其他響應(yīng)信息

復(fù)制代碼

  1. private void SetResponseHeaders(HttpResponseMessage response, FileInfo fileInfo,
  2. long fileLength, string fileName)
  3. {
  4. response.Headers.AcceptRanges.Add("bytes");
  5. response.StatusCode = fileInfo.IsPartial ? HttpStatusCode.PartialContent
  6. : HttpStatusCode.OK;
  7. response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
  8. response.Content.Headers.ContentDisposition.FileName = fileName;
  9. response.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeType);
  10. response.Content.Headers.ContentLength = fileInfo.Length;
  11. if (fileInfo.IsPartial)
  12. {
  13. response.Content.Headers.ContentRange
  14. = new ContentRangeHeaderValue(fileInfo.From, fileInfo.To, fileLength);
  15. }
  16. }

復(fù)制代碼

最重要的一步則是將FileInfo對象的值傳遞給我們自定義實現(xiàn)的流監(jiān)控當(dāng)前下載進度,。

復(fù)制代碼

  1. public class PartialContentFileStream : Stream
  2. {
  3. private readonly long _start;
  4. private readonly long _end;
  5. private long _position;
  6. private FileStream _fileStream;
  7. public PartialContentFileStream(FileStream fileStream, long start, long end)
  8. {
  9. _start = start;
  10. _position = start;
  11. _end = end;
  12. _fileStream = fileStream;
  13. if (start > 0)
  14. {
  15. _fileStream.Seek(start, SeekOrigin.Begin);
  16. }
  17. }
  18. /// <summary>
  19. /// 將緩沖區(qū)數(shù)據(jù)寫到文件
  20. /// </summary>
  21. public override void Flush()
  22. {
  23. _fileStream.Flush();
  24. }
  25. /// <summary>
  26. /// 設(shè)置當(dāng)前下載位置
  27. /// </summary>
  28. /// <param name="offset"></param>
  29. /// <param name="origin"></param>
  30. /// <returns></returns>
  31. public override long Seek(long offset, SeekOrigin origin)
  32. {
  33. if (origin == SeekOrigin.Begin)
  34. {
  35. _position = _start + offset;
  36. return _fileStream.Seek(_start + offset, origin);
  37. }
  38. else if (origin == SeekOrigin.Current)
  39. {
  40. _position += offset;
  41. return _fileStream.Seek(_position + offset, origin);
  42. }
  43. else
  44. {
  45. throw new NotImplementedException("SeekOrigin.End未實現(xiàn)");
  46. }
  47. }
  48. /// <summary>
  49. /// 依據(jù)偏離位置讀取
  50. /// </summary>
  51. /// <param name="buffer"></param>
  52. /// <param name="offset"></param>
  53. /// <param name="count"></param>
  54. /// <returns></returns>
  55. public override int Read(byte[] buffer, int offset, int count)
  56. {
  57. int byteCountToRead = count;
  58. if (_position + count > _end)
  59. {
  60. byteCountToRead = (int)(_end - _position) + 1;
  61. }
  62. var result = _fileStream.Read(buffer, offset, byteCountToRead);
  63. _position += byteCountToRead;
  64. return result;
  65. }
  66. public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
  67. {
  68. int byteCountToRead = count;
  69. if (_position + count > _end)
  70. {
  71. byteCountToRead = (int)(_end - _position);
  72. }
  73. var result = _fileStream.BeginRead(buffer, offset,
  74. count, (s) =>
  75. {
  76. _position += byteCountToRead;
  77. callback(s);
  78. }, state);
  79. return result;
  80. }
  81. ......
  82. }

復(fù)制代碼

更新上述下載的完整邏輯 

復(fù)制代碼

  1. public HttpResponseMessage GetFile(string fileName)
  2. {
  3. fileName = "HBuilder.windows.5.2.6.zip";
  4. if (!FileProvider.Exists(fileName))
  5. {
  6. throw new HttpResponseException(HttpStatusCode.NotFound);
  7. }
  8. long fileLength = FileProvider.GetLength(fileName);
  9. var fileInfo = GetFileInfoFromRequest(this.Request, fileLength);
  10. var stream = new PartialContentFileStream(FileProvider.Open(fileName),
  11. fileInfo.From, fileInfo.To);
  12. var response = new HttpResponseMessage();
  13. response.Content = new StreamContent(stream, BufferSize);
  14. SetResponseHeaders(response, fileInfo, fileLength, fileName);
  15. return response;
  16. }

復(fù)制代碼

下面我們來看看演示結(jié)果:

好了,到了這里我們也得到了我們想要的結(jié)果,。

總結(jié) 

本節(jié)我們將上節(jié)遺留的問題一一進行比較詳細的敘述并最終解決,,是不是就這么完全結(jié)束了呢?那本節(jié)定義為中篇豈不是不對頭了,,本節(jié)是在web端進行下載,,下節(jié)我們利用webclient來進行斷點續(xù)傳。想了想無論是mvc上傳下載,,還是利用webapi來上傳下載又或者是將mvc和webapi結(jié)合來上傳下載基本都已經(jīng)囊括,,這都算是在項目中比較常用的吧,,所以也就花了很多時間去研究。對于webapi的斷點續(xù)傳關(guān)鍵它本身就提供了比較多的api來給我們調(diào)用,,所以還是很不錯,,webapi一個很輕量的服務(wù)框架,你值得擁有see u,,反正周末,,喲,不早了,,休息休息,。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點,。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,,謹防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約