You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 12:37:02