webapi.owinSelfHost文件下载速度缓慢问题排查与解决咨询
问题:OWIN SelfHost WebAPI 文件下载速度过慢(10GE网络仅低速)
我使用webapi.owinSelfHost库搭建Web服务器时,跨计算机下载文件的速度极慢。网络环境为10GE,正常传输速度可达1.4G/s,且自行编写的.NET6 WebAPI程序及客户端测试能达到该速度,但OWIN SelfHost版本无法达到预期。
服务器运行代码
public class RestServer { internal static List<Type> Controllers = new(); public static void Register<T>() where T : ApiController { if (Controllers.IndexOf(typeof(T)) == -1) { Controllers.Add(typeof(T)); } } public static void UnRegister<T>() where T : ApiController { Controllers.Remove(typeof(T)); } public static void Run(string hostName = "localhost", int port = 37701) { if (hostName.StartsWith("http")) { WebApp.Start<RestServer>(hostName); } else { WebApp.Start<RestServer>($"http://{hostName}:{port}"); } } // This code configures Web API. The Startup class is specified as a type // parameter in the WebApp.Start method. public void Configuration(IAppBuilder appBuilder) { // Configure Web API for self-host. HttpConfiguration config = new HttpConfiguration(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); config.Services.Replace(typeof(IHttpControllerTypeResolver), new HttpControllerTypeResolverWithRegister()); appBuilder.UseWebApi(config); } } public class BigDataController : ApiController { public override Task<IHttpActionResult> Get(string id = null) { return Task.Run(() => { try { Program.bigfile.Position = 0; return BuildStream(Program.bigfile); } catch (Exception ex) { Console.WriteLine(ex.GetBaseException().ToString()); throw; } }); } } class StreamResult : IHttpActionResult { Stream _value; HttpRequestMessage _request; public StreamResult(Stream value, HttpRequestMessage request) { _value = value; _request = request; } public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken) { var response = new HttpResponseMessage(); response.RequestMessage = _request; if (_value is null) { response.StatusCode = System.Net.HttpStatusCode.NoContent; } else { _value.Position = 0; response.Content = new System.Net.Http.StreamContent(_value); response.Content.Headers.ContentType=new MediaTypeHeaderValue("application/octet-stream"); } //response.Headers.Add("Content-Type", "application/octet-stream"); return Task.FromResult(response); } }
HTTP响应头对比
OWIN SelfHost应用响应头
Hypertext Transfer Protocol HTTP/1.1 200 OK\r\n Content-Length: 165955581\r\n Content-Type: application/octet-stream\r\n Server: Microsoft-HTTPAPI/2.0\r\n Date: Sat, 16 Sep 2023 08:58:37 GMT\r\n \r\n [HTTP response 1/1] [Time since request: 20.571075000 seconds] [Request in frame: 98824] [Request URI: http://10.0.10.100:37701/api/BigData/122] File Data: 165955581 bytes
.NET6 WebAPI应用响应头
Hypertext Transfer Protocol HTTP/1.1 200 OK\r\n Content-Length: 129549096\r\n Content-Type: application/octet-stream\r\n Date: Sat, 16 Sep 2023 08:57:32 GMT\r\n Server: Kestrel\r\n Content-Disposition: attachment; filename=bigdata; filename*=UTF-8''bigdata\r\n \r\n [HTTP response 1/1] [Time since request: 0.533893000 seconds] [Request in frame: 208] [Request URI: http://10.0.10.100:5156/w] File Data: 129549096 bytes
TCP段数据对比
OWIN SelfHost应用TCP段数据
Transmission Control Protocol, Src Port: 37701, Dst Port: 49706, Seq: 165955151, Ack: 129, Len: 585 Source Port: 37701 Destination Port: 49706 [Stream index: 3] [Conversation completeness: Complete, WITH_DATA (31)] [TCP Segment Len: 585] Sequence Number: 165955151 (relative sequence number) Sequence Number (raw): 2754326255 [Next Sequence Number: 165955736 (relative sequence number)] Acknowledgment Number: 129 (relative ack number) Acknowledgment number (raw): 3724955552 0101 .... = Header Length: 20 bytes (5) Flags: 0x018 (PSH, ACK) Window: 8212 [Calculated window size: 2102272] [Window size scaling factor: 256] Checksum: 0x2b2c [unverified] [Checksum Status: Unverified] Urgent Pointer: 0 [Timestamps] [SEQ/ACK analysis] TCP payload (585 bytes) TCP segment data (585 bytes)
.NET6 WebAPI应用TCP段数据
Transmission Control Protocol, Src Port: 5156, Dst Port: 49705, Seq: 129548192, Ack: 44, Len: 1122 Source Port: 5156 Destination Port: 49705 [Stream index: 1] [Conversation completeness: Complete, WITH_DATA (47)] [TCP Segment Len: 1122] Sequence Number: 129548192 (relative sequence number) Sequence Number (raw): 1706970176 [Next Sequence Number: 129549314 (relative sequence number)] Acknowledgment Number: 44 (relative ack number) Acknowledgment number (raw): 3207951358 0101 .... = Header Length: 20 bytes (5) Flags: 0x018 (PSH, ACK) Window: 8212 [Calculated window size: 2102272] [Window size scaling factor: 256] Checksum: 0x2d45 [unverified] [Checksum Status: Unverified] Urgent Pointer: 0 [Timestamps] [SEQ/ACK analysis] TCP payload (1122 bytes) TCP segment data (1122 bytes)
解决方案
1. 调整StreamContent的缓冲区大小(核心修复)
StreamContent默认使用的缓冲区太小(通常4KB左右),导致每次仅发送少量数据,TCP段长度被限制,无法利用10GE带宽。创建StreamContent时指定更大的缓冲区(如64KB或128KB):
// 修改StreamResult中的响应内容创建逻辑 response.Content = new StreamContent(_value, 65536); // 64KB缓冲区,可根据网络调整
2. 移除不必要的Task.Run
控制器的Get方法无需用Task.Run包装,直接改为异步写法,避免额外线程开销:
public async Task<IHttpActionResult> Get(string id = null) { try { Program.bigfile.Position = 0; return new StreamResult(Program.bigfile, Request); } catch (Exception ex) { Console.WriteLine(ex.GetBaseException().ToString()); throw; } }
3. 优化文件流的打开方式
确保打开大文件时使用顺序扫描模式,并设置合适的读取缓冲区,提升文件读取效率:
// 在Program中初始化bigfile时调整 Program.bigfile = new FileStream( "path/to/your/bigfile", FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 65536, FileOptions.SequentialScan );
4. 避免重复重置流位置
在BigDataController和StreamResult中重复设置Position=0是冗余操作,保留一次即可(比如仅在控制器中设置)。
原理说明
从TCP段数据对比可见,OWIN版本的TCP payload仅585字节,远小于Kestrel的1122字节,根源在于小缓冲区导致的频繁小数据包发送,无法填满网络带宽。增大缓冲区后,每次发送的数据量提升,TCP段长度更合理,能充分利用10GE网络的传输能力。
内容的提问来源于stack exchange,提问作者zhongxiang qian
相关产品推荐
相关产品推荐

