当前位置: 首页 > news >正文

AI时代的算法思维:大经典排序学习

这类问题之所以经久不衰,是因为它在低并发下几乎永远跑得通;但一旦进入“高并发 + 短连接密集创建”的场景,就会突然变成玄学:有的人能跑,有的人会炸,有人说这是一个这是一个“bug”,在某某版本中会修复(其实并没有),有人说这是一个feature,设计就是如此……

所以我决定做一个实验,来重现一下10年前就有的现象,看这些现象是否有任何不同。

本文用一组可复现的压测(同机 server/client,Windows,requests=20000,parallel=200)对比:

每请求 new HttpClient + Dispose()(也就是大家常说的“using 写法”)

复用一个 HttpClient(静态/单例)

使用 IHttpClientFactory

并观察关键指标:TIME_WAIT 数量,以及是否出现经典的端口耗尽错误:

通常每个套接字地址(协议/网络地址/端口)只允许使用一次。

实验目标

验证“每请求 new HttpClient 并 using 释放”在高并发下会导致 TIME_WAIT 激增,并对比复用 HttpClient / IHttpClientFactory 的表现。

实验环境与参数

OS: Windows

SDK: .NET SDK 10.0.102

服务器: HttpLeakServer(target net6,通过 roll-forward 运行)

客户端: net48 / net6 / net8 / net10

压测参数: requests=20000, parallel=200, timeoutSeconds=5

TIME_WAIT 统计: netstat -an 过滤端口 5055

隔离策略: 每轮结束后等待 TIME_WAIT <= baseline + 200(每 10 秒检查,最长 300 秒)

如何运行(可复现)

完整项目我放在 GitHub:https://github.com/sdcb/http-client-exp

1)启动服务端(单独窗口)

dotnet run --project Server/HttpLeakServer/HttpLeakServer.csproj

2)运行实验脚本(另一个窗口)

scripts/run-experiment-external-server.ps1

本次日志目录:logs/run-20260119-095017

解读:为什么“using HttpClient”会把你推向端口耗尽?

很多人直觉会觉得:HttpClient 是托管对象,用完 Dispose(),不就释放资源了吗?

但这里有两个关键点经常被忽略:

HttpClient 并不是“请求一次就关一次连接”的简单模型。HTTP Keep-Alive + 连接池的存在,意味着正确姿势应该是复用底层连接(或至少复用 handler 的连接池),让大量请求复用少量 TCP 连接。

你频繁 new HttpClient + Dispose(),等价于频繁建立 TCP 连接并快速关闭。而 TCP 连接的关闭会进入 TIME_WAIT(具体哪一端进入 TIME_WAIT 与关闭时序有关),TIME_WAIT 存在的意义是保护网络不被“旧连接的残留包”污染。

在“200 并发 + 2 万次请求”这种参数下,如果你让每个请求都创建新连接,那么很容易短时间制造大量 TIME_WAIT;一旦本机可用的临时端口范围被 TIME_WAIT 占满(或接近占满),新连接就会开始失败,典型异常就是:

通常每个套接字地址(协议/网络地址/端口)只允许使用一次。

这也是为什么同样的代码:

在低并发下“看起来完全没问题”

在压力一上来就开始“玄学报错”

那到底怎么写才对?

本文不展开“所有场景的最佳实践”,只给两条最能落地的结论:

业务代码不要每次请求 new HttpClient。要么复用单例/静态 HttpClient,要么使用 IHttpClientFactory。

可以 using 的是 HttpResponseMessage / HttpContent(它们确实应该及时释放),而不是“每个请求一个 HttpClient”。

下面我把这次实验的完整代码和原始日志全部贴出来,方便你自己复跑/改参数/做二次验证。

完整代码(精简版:一份源码 + 条件编译)

完整项目地址:https://github.com/sdcb/http-client-exp

为了避免同样的代码贴四遍,这里把 net48/net6/net8/net10 的客户端合并成一份,用 #if / #elif 表示差异(实验输出与原始日志仍按“多份结果”原样保留在后文)。

简单服务端:HttpLeakServer

Server/HttpLeakServer/HttpLeakServer.csproj

net6.0

enable

enable

Server/HttpLeakServer/Program.cs

using Microsoft.AspNetCore.Builder;

using Microsoft.Extensions.Hosting;

using Microsoft.Extensions.Logging;

var builder = WebApplication.CreateBuilder(args);

builder.Logging.ClearProviders();

builder.Logging.AddSimpleConsole(options =>

{

options.SingleLine = true;

options.TimestampFormat = "HH:mm:ss ";

});

var app = builder.Build();

app.MapGet("/", () => Results.Text("ok"));

app.MapGet("/ping", () => Results.Text("ok"));

app.MapGet("/slow", async () =>

{

await Task.Delay(50);

return Results.Text("ok");

});

var url = Environment.GetEnvironmentVariable("HTTPLEAK_URL") ?? "http://localhost:5055";

app.Urls.Add(url);

app.Lifetime.ApplicationStarted.Register(() =>

{

Console.WriteLine($"Listening on {url}");

});

await app.RunAsync();

客户端:HttpLeakClient(net48/net6/net8/net10 共用一份)

Clients/HttpLeakClient/HttpLeakClient.csproj(示意:多目标 + 条件依赖)

Exe

net48;net6.0;net8.0;net10.0

latest

enable

enable

Clients/HttpLeakClient/Program.cs(用 #if 表示差异)

using System.Diagnostics;

using System.Net.Http;

#if NET48

using System.Net;

#endif

#if NET10_0_OR_GREATER

using Microsoft.Extensions.DependencyInjection;

#endif

static string? GetArg(string[] args, string name)

{

for (var i = 0; i < args.Length - 1; i++)

{

if (string.Equals(args[i], name, StringComparison.OrdinalIgnoreCase))

{

return args[i + 1];

}

}

return null;

}

static int GetArgInt(string[] args, string name, int defaultValue)

{

var value = GetArg(args, name);

return int.TryParse(value, out var parsed) ? parsed : defaultValue;

}

var url = GetArg(args, "--url") ?? "http://localhost:5055/ping";

var requests = GetArgInt(args, "--requests", 20000);

var parallel = GetArgInt(args, "--parallel", 200);

var logEvery = GetArgInt(args, "--logEvery", 1000);

var timeoutSeconds = GetArgInt(args, "--timeoutSeconds", 5);

#if NET10_0_OR_GREATER

var mode = GetArg(args, "--mode") ?? "new"; // new | static | factory

#endif

Console.WriteLine($"url={url}");

#if NET10_0_OR_GREATER

Console.WriteLine($"requests={requests}, parallel={parallel}, timeoutSeconds={timeoutSeconds}, mode={mode}");

#else

Console.WriteLine($"requests={requests}, parallel={parallel}, timeoutSeconds={timeoutSeconds}");

#endif

#if NET48

ServicePointManager.DefaultConnectionLimit = 1000;

ServicePointManager.Expect100Continue = false;

#endif

var throttler = new SemaphoreSlim(parallel);

var tasks = new List(requests);

var sw = Stopwatch.StartNew();

var success = 0;

var failed = 0;

#if NET10_0_OR_GREATER

HttpClient? staticClient = null;

if (string.Equals(mode, "static", StringComparison.OrdinalIgnoreCase))

{

staticClient = new HttpClient

{

Timeout = TimeSpan.FromSeconds(timeoutSeconds)

};

}

IHttpClientFactory? httpClientFactory = null;

ServiceProvider? serviceProvider = null;

if (string.Equals(mode, "factory", StringComparison.OrdinalIgnoreCase))

{

var services = new ServiceCollection();

services.AddHttpClient();

serviceProvider = services.BuildServiceProvider();

httpClientFactory = serviceProvider.GetRequiredService();

}

#endif

for (var i = 0; i < requests; i++)

{

await throttler.WaitAsync();

var index = i + 1;

tasks.Add(Task.Run(async () =>

{

try

{

#if NET10_0_OR_GREATER

HttpClient client;

if (staticClient != null)

{

client = staticClient;

}

else if (httpClientFactory != null)

{

client = httpClientFactory.CreateClient();

client.Timeout = TimeSpan.FromSeconds(timeoutSeconds);

}

else

{

client = new HttpClient();

client.Timeout = TimeSpan.FromSeconds(timeoutSeconds);

}

using var response = await client.GetAsync(url);

response.EnsureSuccessStatusCode();

Interlocked.Increment(ref success);

if (staticClient == null && httpClientFactory == null)

{

client.Dispose();

}

#else

using var client = new HttpClient();

client.Timeout = TimeSpan.FromSeconds(timeoutSeconds);

using var response = await client.GetAsync(url);

response.EnsureSuccessStatusCode();

Interlocked.Increment(ref success);

#endif

}

catch (Exception ex)

{

var fail = Interlocked.Increment(ref failed);

if (fail <= 5)

{

Console.WriteLine($"ERR#{fail}: {ex.GetType().Name} {ex.Message}");

}

}

finally

{

throttler.Release();

}

}));

if (index % logEvery == 0)

{

Console.WriteLine($"queued: {index}/{requests}, success: {Volatile.Read(ref success)}, failed: {Volatile.Read(ref failed)}, elapsed: {sw.Elapsed}");

}

}

await Task.WhenAll(tasks);

Console.WriteLine($"done: success={success}, failed={failed}, elapsed={sw.Elapsed}");

#if NET10_0_OR_GREATER

staticClient?.Dispose();

serviceProvider?.Dispose();

#endif

PowerShell 脚本

脚本我就不在文章里全文贴了:

外部启动 Server 版本:https://github.com/sdcb/http-client-exp/blob/main/scripts/run-experiment-external-server.ps1

脚本内启动 Server 版本:https://github.com/sdcb/http-client-exp/blob/main/scripts/run-experiment.ps1

原始实验结果(日志,完整贴出)

日志目录:logs/run-20260119-095017

experiment-summary.log

[2026-01-19T09:51:13.7180857+08:00] net48 ExitCode=0 OutLog=C:\Users\ZhouJie\source\repos\http-client-exp\logs\\run-20260119-095017\net48.out.log ErrLog=C:\Users\ZhouJie\source\repos\http-client-exp\logs\\run-20260119-095017\net48.err.log

[2026-01-19T09:53:33.0362523+08:00] net6 ExitCode=0 OutLog=C:\Users\ZhouJie\source\repos\http-client-exp\logs\\run-20260119-095017\net6.out.log ErrLog=C:\Users\ZhouJie\source\repos\http-client-exp\logs\\run-20260119-095017\net6.err.log

[2026-01-19T09:56:00.4574527+08:00] net8 ExitCode=0 OutLog=C:\Users\ZhouJie\source\repos\http-client-exp\logs\\run-20260119-095017\net8.out.log ErrLog=C:\Users\ZhouJie\source\repos\http-client-exp\logs\\run-20260119-095017\net8.err.log

[2026-01-19T09:58:27.2618139+08:00] net10-new ExitCode=0 OutLog=C:\Users\ZhouJie\source\repos\http-client-exp\logs\\run-20260119-095017\net10-new.out.log ErrLog=C:\Users\ZhouJie\source\repos\http-client-exp\logs\\run-20260119-095017\net10-new.err.log

[2026-01-19T10:00:53.0490472+08:00] net10-static ExitCode=0 OutLog=C:\Users\ZhouJie\source\repos\http-client-exp\logs\\run-20260119-095017\net10-static.out.log ErrLog=C:\Users\ZhouJie\source\repos\http-client-exp\logs\\run-20260119-095017\net10-static.err.log

[2026-01-19T10:01:10.2096164+08:00] net10-factory ExitCode=0 OutLog=C:\Users\ZhouJie\source\repos\http-client-exp\logs\\run-20260119-095017\net10-factory.out.log ErrLog=C:\Users\ZhouJie\source\repos\http-client-exp\logs\\run-20260119-095017\net10-factory.err.log

netstat.log

[2026-01-19T09:50:55.6627455+08:00] before-net48 TIME_WAIT=2 ESTABLISHED=0 CLOSE_WAIT=0

[2026-01-19T09:51:15.4686603+08:00] after-net48 TIME_WAIT=20002 ESTABLISHED=0 CLOSE_WAIT=0

[2026-01-19T09:53:13.9647485+08:00] before-net6 TIME_WAIT=0 ESTABLISHED=0 CLOSE_WAIT=0

[2026-01-19T09:53:33.8112294+08:00] after-net6 TIME_WAIT=20000 ESTABLISHED=0 CLOSE_WAIT=0

[2026-01-19T09:55:42.6435015+08:00] before-net8 TIME_WAIT=0 ESTABLISHED=0 CLOSE_WAIT=0

[2026-01-19T09:56:01.1900000+08:00] after-net8 TIME_WAIT=18361 ESTABLISHED=0 CLOSE_WAIT=0

[2026-01-19T09:58:08.8603129+08:00] before-net10-new TIME_WAIT=0 ESTABLISHED=0 CLOSE_WAIT=0

[2026-01-19T09:58:28.1500737+08:00] after-net10-new TIME_WAIT=18860 ESTABLISHED=0 CLOSE_WAIT=0

[2026-01-19T10:00:36.1038757+08:00] before-net10-static TIME_WAIT=0 ESTABLISHED=0 CLOSE_WAIT=0

[2026-01-19T10:00:53.1007259+08:00] after-net10-static TIME_WAIT=200 ESTABLISHED=0 CLOSE_WAIT=0

[2026-01-19T10:00:53.2458798+08:00] before-net10-factory TIME_WAIT=200 ESTABLISHED=0 CLOSE_WAIT=0

[2026-01-19T10:01:10.2574678+08:00] after-net10-factory TIME_WAIT=200 ESTABLISHED=0 CLOSE_WAIT=0

cooldown.log

[2026-01-19T09:51:16.2687666+08:00] cooldown-net48 TIME_WAIT=20002 baseline=2 limit=202

[2026-01-19T09:51:27.0427805+08:00] cooldown-net48 TIME_WAIT=20002 baseline=2 limit=202

[2026-01-19T09:51:37.8184382+08:00] cooldown-net48 TIME_WAIT=20002 baseline=2 limit=202

[2026-01-19T09:51:48.5404492+08:00] cooldown-net48 TIME_WAIT=20002 baseline=2 limit=202

[2026-01-19T09:51:59.2846968+08:00] cooldown-net48 TIME_WAIT=20002 baseline=2 limit=202

[2026-01-19T09:52:10.1562079+08:00] cooldown-net48 TIME_WAIT=20002 baseline=2 limit=202

[2026-01-19T09:52:21.0588622+08:00] cooldown-net48 TIME_WAIT=20002 baseline=2 limit=202

[2026-01-19T09:52:31.7777704+08:00] cooldown-net48 TIME_WAIT=20002 baseline=2 limit=202

[2026-01-19T09:52:42.5664350+08:00] cooldown-net48 TIME_WAIT=20002 baseline=2 limit=202

[2026-01-19T09:52:53.3299614+08:00] cooldown-net48 TIME_WAIT=20001 baseline=2 limit=202

[2026-01-19T09:53:03.8109734+08:00] cooldown-net48 TIME_WAIT=10752 baseline=2 limit=202

[2026-01-19T09:53:13.8560831+08:00] cooldown-net48 TIME_WAIT=0 baseline=2 limit=202

[2026-01-19T09:53:34.6370711+08:00] cooldown-net6 TIME_WAIT=20000 baseline=0 limit=200

[2026-01-19T09:53:45.5166974+08:00] cooldown-net6 TIME_WAIT=20000 baseline=0 limit=200

[2026-01-19T09:53:56.2899736+08:00] cooldown-net6 TIME_WAIT=20000 baseline=0 limit=200

[2026-01-19T09:54:07.0165428+08:00] cooldown-net6 TIME_WAIT=20000 baseline=0 limit=200

[2026-01-19T09:54:17.8872137+08:00] cooldown-net6 TIME_WAIT=20000 baseline=0 limit=200

[2026-01-19T09:54:28.7673529+08:00] cooldown-net6 TIME_WAIT=20000 baseline=0 limit=200

[2026-01-19T09:54:39.5200091+08:00] cooldown-net6 TIME_WAIT=20000 baseline=0 limit=200

[2026-01-19T09:54:50.3090659+08:00] cooldown-net6 TIME_WAIT=20000 baseline=0 limit=200

[2026-01-19T09:55:01.0638920+08:00] cooldown-net6 TIME_WAIT=20000 baseline=0 limit=200

[2026-01-19T09:55:11.7529528+08:00] cooldown-net6 TIME_WAIT=20000 baseline=0 limit=200

[2026-01-19T09:55:22.3886445+08:00] cooldown-net6 TIME_WAIT=12540 baseline=0 limit=200

[2026-01-19T09:55:32.4658786+08:00] cooldown-net6 TIME_WAIT=435 baseline=0 limit=200

[2026-01-19T09:55:42.5354878+08:00] cooldown-net6 TIME_WAIT=0 baseline=0 limit=200

[2026-01-19T09:56:01.8876493+08:00] cooldown-net8 TIME_WAIT=18361 baseline=0 limit=200

[2026-01-19T09:56:12.6341541+08:00] cooldown-net8 TIME_WAIT=18361 baseline=0 limit=200

[2026-01-19T09:56:23.2806604+08:00] cooldown-net8 TIME_WAIT=18361 baseline=0 limit=200

[2026-01-19T09:56:33.9425873+08:00] cooldown-net8 TIME_WAIT=18361 baseline=0 limit=200

[2026-01-19T09:56:44.6277561+08:00] cooldown-net8 TIME_WAIT=18361 baseline=0 limit=200

[2026-01-19T09:56:55.3013674+08:00] cooldown-net8 TIME_WAIT=18361 baseline=0 limit=200

[2026-01-19T09:57:06.0278128+08:00] cooldown-net8 TIME_WAIT=18361 baseline=0 limit=200

[2026-01-19T09:57:16.7315578+08:00] cooldown-net8 TIME_WAIT=18361 baseline=0 limit=200

[2026-01-19T09:57:27.3887925+08:00] cooldown-net8 TIME_WAIT=18361 baseline=0 limit=200

[2026-01-19T09:57:38.0442453+08:00] cooldown-net8 TIME_WAIT=18361 baseline=0 limit=200

[2026-01-19T09:57:48.6566839+08:00] cooldown-net8 TIME_WAIT=12989 baseline=0 limit=200

[2026-01-19T09:57:58.7347337+08:00] cooldown-net8 TIME_WAIT=1069 baseline=0 limit=200

[2026-01-19T09:58:08.7780660+08:00] cooldown-net8 TIME_WAIT=0 baseline=0 limit=200

[2026-01-19T09:58:29.0261291+08:00] cooldown-net10-new TIME_WAIT=18860 baseline=0 limit=200

[2026-01-19T09:58:39.7491043+08:00] cooldown-net10-new TIME_WAIT=18860 baseline=0 limit=200

[2026-01-19T09:58:50.4613796+08:00] cooldown-net10-new TIME_WAIT=18860 baseline=0 limit=200

[2026-01-19T09:59:01.2064545+08:00] cooldown-net10-new TIME_WAIT=18860 baseline=0 limit=200

[2026-01-19T09:59:11.8710153+08:00] cooldown-net10-new TIME_WAIT=18860 baseline=0 limit=200

[2026-01-19T09:59:22.5648486+08:00] cooldown-net10-new TIME_WAIT=18860 baseline=0 limit=200

[2026-01-19T09:59:33.2755958+08:00] cooldown-net10-new TIME_WAIT=18860 baseline=0 limit=200

[2026-01-19T09:59:43.9631547+08:00] cooldown-net10-new TIME_WAIT=18860 baseline=0 limit=200

[2026-01-19T09:59:54.6979243+08:00] cooldown-net10-new TIME_WAIT=18860 baseline=0 limit=200

[2026-01-19T10:00:05.3961397+08:00] cooldown-net10-new TIME_WAIT=18860 baseline=0 limit=200

[2026-01-19T10:00:15.8607378+08:00] cooldown-net10-new TIME_WAIT=11840 baseline=0 limit=200

[2026-01-19T10:00:25.9471407+08:00] cooldown-net10-new TIME_WAIT=614 baseline=0 limit=200

[2026-01-19T10:00:36.0111701+08:00] cooldown-net10-new TIME_WAIT=0 baseline=0 limit=200

[2026-01-19T10:00:53.1510649+08:00] cooldown-net10-static TIME_WAIT=200 baseline=0 limit=200

[2026-01-19T10:01:10.3057785+08:00] cooldown-net10-factory TIME_WAIT=200 baseline=200 limit=400

net48.out.log

url=http://localhost:5055/ping

requests=20000, parallel=200, timeoutSeconds=5

queued: 1000/20000, success: 803, failed: 0, elapsed: 00:00:00.7170409

queued: 2000/20000, success: 1803, failed: 0, elapsed: 00:00:01.5455465

queued: 3000/20000, success: 2800, failed: 0, elapsed: 00:00:02.3559601

queued: 4000/20000, success: 3801, failed: 0, elapsed: 00:00:03.1925172

queued: 5000/20000, success: 4802, failed: 0, elapsed: 00:00:04.0184093

queued: 6000/20000, success: 5802, failed: 0, elapsed: 00:00:04.8348862

queued: 7000/20000, success: 6800, failed: 0, elapsed: 00:00:05.6775563

queued: 8000/20000, success: 7801, failed: 0, elapsed: 00:00:06.5458377

queued: 9000/20000, success: 8802, failed: 0, elapsed: 00:00:07.4397727

queued: 10000/20000, success: 9803, failed: 0, elapsed: 00:00:08.3348468

queued: 11000/20000, success: 10802, failed: 0, elapsed: 00:00:09.2035416

queued: 12000/20000, success: 11804, failed: 0, elapsed: 00:00:10.0393223

queued: 13000/20000, success: 12806, failed: 0, elapsed: 00:00:10.8262272

queued: 14000/20000, success: 13804, failed: 0, elapsed: 00:00:11.6636597

queued: 15000/20000, success: 14802, failed: 0, elapsed: 00:00:12.4799716

queued: 16000/20000, success: 15819, failed: 0, elapsed: 00:00:13.4697834

queued: 17000/20000, success: 16822, failed: 0, elapsed: 00:00:14.5332049

queued: 18000/20000, success: 17818, failed: 0, elapsed: 00:00:15.5317397

queued: 19000/20000, success: 18816, failed: 0, elapsed: 00:00:16.5405963

queued: 20000/20000, success: 19819, failed: 0, elapsed: 00:00:17.6652081

done: success=20000, failed=0, elapsed=00:00:17.7541839

net6.out.log

url=http://localhost:5055/ping

requests=20000, parallel=200, timeoutSeconds=5

queued: 1000/20000, success: 803, failed: 0, elapsed: 00:00:00.7159800

queued: 2000/20000, success: 1803, failed: 0, elapsed: 00:00:01.6028433

queued: 3000/20000, success: 2802, failed: 0, elapsed: 00:00:02.4876237

queued: 4000/20000, success: 3802, failed: 0, elapsed: 00:00:03.3549543

queued: 5000/20000, success: 4802, failed: 0, elapsed: 00:00:04.2710795

queued: 6000/20000, success: 5801, failed: 0, elapsed: 00:00:05.1637653

queued: 7000/20000, success: 6802, failed: 0, elapsed: 00:00:06.0729777

queued: 8000/20000, success: 7802, failed: 0, elapsed: 00:00:07.0697555

queued: 9000/20000, success: 8801, failed: 0, elapsed: 00:00:08.0143162

queued: 10000/20000, success: 9800, failed: 0, elapsed: 00:00:08.9633860

queued: 11000/20000, success: 10802, failed: 0, elapsed: 00:00:09.9344239

queued: 12000/20000, success: 11802, failed: 0, elapsed: 00:00:10.8379783

queued: 13000/20000, success: 12801, failed: 0, elapsed: 00:00:11.6810601

queued: 14000/20000, success: 13801, failed: 0, elapsed: 00:00:12.5122642

queued: 15000/20000, success: 14800, failed: 0, elapsed: 00:00:13.3692282

queued: 16000/20000, success: 15803, failed: 0, elapsed: 00:00:14.2782829

queued: 17000/20000, success: 16801, failed: 0, elapsed: 00:00:15.2187642

queued: 18000/20000, success: 17802, failed: 0, elapsed: 00:00:16.0811154

queued: 19000/20000, success: 18810, failed: 0, elapsed: 00:00:16.9798536

queued: 20000/20000, success: 19817, failed: 0, elapsed: 00:00:17.8952478

done: success=20000, failed=0, elapsed=00:00:18.0175351

net8.out.log

url=http://localhost:5055/ping

requests=20000, parallel=200, timeoutSeconds=5

queued: 1000/20000, success: 800, failed: 0, elapsed: 00:00:00.8405997

queued: 2000/20000, success: 1802, failed: 0, elapsed: 00:00:01.6913251

queued: 3000/20000, success: 2800, failed: 0, elapsed: 00:00:02.6176324

queued: 4000/20000, success: 3802, failed: 0, elapsed: 00:00:03.4744051

queued: 5000/20000, success: 4801, failed: 0, elapsed: 00:00:04.2873345

queued: 6000/20000, success: 5800, failed: 0, elapsed: 00:00:05.0960137

queued: 7000/20000, success: 6803, failed: 0, elapsed: 00:00:05.8940500

queued: 8000/20000, success: 7802, failed: 0, elapsed: 00:00:06.7207568

queued: 9000/20000, success: 8803, failed: 0, elapsed: 00:00:07.5346954

queued: 10000/20000, success: 9800, failed: 0, elapsed: 00:00:08.3507870

queued: 11000/20000, success: 10802, failed: 0, elapsed: 00:00:09.1985154

queued: 12000/20000, success: 11803, failed: 0, elapsed: 00:00:10.0035216

queued: 13000/20000, success: 12803, failed: 0, elapsed: 00:00:10.7763067

queued: 14000/20000, success: 13802, failed: 0, elapsed: 00:00:11.6251384

queued: 15000/20000, success: 14802, failed: 0, elapsed: 00:00:12.4436652

queued: 16000/20000, success: 15807, failed: 0, elapsed: 00:00:13.3164599

queued: 17000/20000, success: 16801, failed: 0, elapsed: 00:00:14.2231530

ERR#1: HttpRequestException 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。 (localhost:5055)

ERR#2: HttpRequestException 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。 (localhost:5055)

ERR#3: HttpRequestException 通常每个套接 地址(协议/网络地址/端口)只允许使用一次。 (localhost:5055)

ERR#5: HttpRequestException 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。 (localhost:5055)

ERR#4: HttpRequestException 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。 (localhost:5055)

queued: 18000/20000, success: 17605, failed: 201, elapsed: 00:00:15.0862152

queued: 19000/20000, success: 17711, failed: 1090, elapsed: 00:00:15.6168905

queued: 20000/20000, success: 18165, failed: 1639, elapsed: 00:00:16.3332350

done: success=18361, failed=1639, elapsed=00:00:16.4268168

net10-new.out.log

url=http://localhost:5055/ping

requests=20000, parallel=200, timeoutSeconds=5, mode=new

queued: 1000/20000, success: 805, failed: 0, elapsed: 00:00:00.7099803

queued: 2000/20000, success: 1800, failed: 0, elapsed: 00:00:01.5324361

queued: 3000/20000, success: 2800, failed: 0, elapsed: 00:00:02.3573877

queued: 4000/20000, success: 3800, failed: 0, elapsed: 00:00:03.2069000

queued: 5000/20000, success: 4800, failed: 0, elapsed: 00:00:04.0313423

queued: 6000/20000, success: 5802, failed: 0, elapsed: 00:00:04.8687039

queued: 7000/20000, success: 6801, failed: 0, elapsed: 00:00:05.7252572

queued: 8000/20000, success: 7800, failed: 0, elapsed: 00:00:06.5624078

queued: 9000/20000, success: 8800, failed: 0, elapsed: 00:00:07.4244971

queued: 10000/20000, success: 9800, failed: 0, elapsed: 00:00:08.2911306

queued: 11000/20000, success: 10800, failed: 0, elapsed: 00:00:09.1755667

queued: 12000/20000, success: 11801, failed: 0, elapsed: 00:00:10.1160925

queued: 13000/20000, success: 12802, failed: 0, elapsed: 00:00:11.0165038

queued: 14000/20000, success: 13801, failed: 0, elapsed: 00:00:11.9382103

queued: 15000/20000, success: 14802, failed: 0, elapsed: 00:00:12.8002543

queued: 16000/20000, success: 15801, failed: 0, elapsed: 00:00:13.7185127

ERR#3: HttpRequestException 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。 (localhost:5055)

ERR#2: HttpRequestException 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。 (localhost:5055)

ERR#1: HttpRequestException 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。 (localhost:5055)

ERR#5: HttpRequestException 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。 (localhost:5055)

ERR#4: HttpRequestException 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。 (localhost:5055)

queued: 17000/20000, success: 16682, failed: 118, elapsed: 00:00:14.7578539

queued: 18000/20000, success: 16684, failed: 1118, elapsed: 00:00:15.4297844

queued: 19000/20000, success: 17661, failed: 1140, elapsed: 00:00:16.2796589

queued: 20000/20000, success: 18661, failed: 1140, elapsed: 00:00:17.3258406

done: success=18860, failed=1140, elapsed=00:00:17.3996862

net10-static.out.log

url=http://localhost:5055/ping

requests=20000, parallel=200, timeoutSeconds=5, mode=static

queued: 1000/20000, success: 801, failed: 0, elapsed: 00:00:00.6551470

queued: 2000/20000, success: 1800, failed: 0, elapsed: 00:00:01.5355394

queued: 3000/20000, success: 2800, failed: 0, elapsed: 00:00:02.3480456

queued: 4000/20000, success: 3800, failed: 0, elapsed: 00:00:03.0999050

queued: 5000/20000, success: 4800, failed: 0, elapsed: 00:00:03.8470820

queued: 6000/20000, success: 5800, failed: 0, elapsed: 00:00:04.5759884

queued: 7000/20000, success: 6800, failed: 0, elapsed: 00:00:05.3461919

queued: 8000/20000, success: 7801, failed: 0, elapsed: 00:00:06.0916621

queued: 9000/20000, success: 8802, failed: 0, elapsed: 00:00:06.9085343

queued: 10000/20000, success: 9800, failed: 0, elapsed: 00:00:07.8274125

queued: 11000/20000, success: 10800, failed: 0, elapsed: 00:00:08.6757231

queued: 12000/20000, success: 11800, failed: 0, elapsed: 00:00:09.5154490

queued: 13000/20000, success: 12800, failed: 0, elapsed: 00:00:10.3306765

queued: 14000/20000, success: 13800, failed: 0, elapsed: 00:00:11.1493724

queued: 15000/20000, success: 14800, failed: 0, elapsed: 00:00:11.9658212

queued: 16000/20000, success: 15801, failed: 0, elapsed: 00:00:12.7510706

queued: 17000/20000, success: 16800, failed: 0, elapsed: 00:00:13.4735304

queued: 18000/20000, success: 17800, failed: 0, elapsed: 00:00:14.2777953

queued: 19000/20000, success: 18800, failed: 0, elapsed: 00:00:15.0219907

queued: 20000/20000, success: 19801, failed: 0, elapsed: 00:00:15.7699702

done: success=20000, failed=0, elapsed=00:00:15.8507052

net10-factory.out.log

url=http://localhost:5055/ping

requests=20000, parallel=200, timeoutSeconds=5, mode=factory

queued: 1000/20000, success: 801, failed: 0, elapsed: 00:00:00.7094395

queued: 2000/20000, success: 1801, failed: 0, elapsed: 00:00:01.5072383

queued: 3000/20000, success: 2800, failed: 0, elapsed: 00:00:02.3047647

queued: 4000/20000, success: 3800, failed: 0, elapsed: 00:00:03.0607252

queued: 5000/20000, success: 4800, failed: 0, elapsed: 00:00:03.8370598

queued: 6000/20000, success: 5800, failed: 0, elapsed: 00:00:04.6621606

queued: 7000/20000, success: 6800, failed: 0, elapsed: 00:00:05.4589104

queued: 8000/20000, success: 7800, failed: 0, elapsed: 00:00:06.2913588

queued: 9000/20000, success: 8800, failed: 0, elapsed: 00:00:07.0629536

queued: 10000/20000, success: 9800, failed: 0, elapsed: 00:00:07.8438472

queued: 11000/20000, success: 10800, failed: 0, elapsed: 00:00:08.5796209

queued: 12000/20000, success: 11800, failed: 0, elapsed: 00:00:09.3663975

queued: 13000/20000, success: 12801, failed: 0, elapsed: 00:00:10.1984757

queued: 14000/20000, success: 13800, failed: 0, elapsed: 00:00:11.0474925

queued: 15000/20000, success: 14800, failed: 0, elapsed: 00:00:11.9175753

queued: 16000/20000, success: 15800, failed: 0, elapsed: 00:00:12.7356429

queued: 17000/20000, success: 16801, failed: 0, elapsed: 00:00:13.4991148

queued: 18000/20000, success: 17800, failed: 0, elapsed: 00:00:14.2912941

queued: 19000/20000, success: 18800, failed: 0, elapsed: 00:00:15.0844828

queued: 20000/20000, success: 19800, failed: 0, elapsed: 00:00:15.8439387

done: success=20000, failed=0, elapsed=00:00:15.9485251

局限与备注(别把结论用错地方)

客户端与服务器同机:TIME_WAIT 统计包含两端连接,不能完全归因于“客户端端口耗尽”,但它足以说明“短时间制造大量短连接”这件事本身的风险。

net48 设置了 ServicePointManager.DefaultConnectionLimit = 1000,与 net6+/net10 的连接管理策略存在差异。

关键结果汇总

TIME_WAIT 统计(端口 5055)

运行 before TIME_WAIT after TIME_WAIT 耗时(秒)

net48 2 20002 17.754

net6 0 20000 18.018

net8 0 18361 16.427

net10 0 18860 17.400

net10-static 0 200 15.851

net10-factory 200 200 15.949

elapsed_seconds

time_wait_delta

来源:logs/run-20260119-095017/netstat.log

客户端执行结果(摘要)

net48: success=20000, failed=0

net6: success=20000, failed=0

net8: success=18361, failed=1639(报错:“通常每个套接字地址只允许使用一次”)

net10-new: success=18860, failed=1140(报错:“通常每个套接字地址只允许使用一次”)

net10-static: success=20000, failed=0

net10-factory: success=20000, failed=0

来源:logs/run-20260119-095017/*.out.log

结论

“.NET 10 了,HttpClient 还能不能 using?”——答案依然是:别把 HttpClient 当成一次性对象。枷磁剿僚

http://www.cnnetsun.cn/news/1626804.html

相关文章:

  • Unity翻译引擎:实时翻译与游戏本地化的多框架适配解决方案
  • 微信聊天记录永久保存与深度分析:WeChatMsg让你的数字记忆不再流失
  • HsMod炉石传说插件高效实践指南:从安装到定制的全流程解决方案
  • OpCore-Simplify:如何让黑苹果爱好者通过自动化流程实现95%配置效率提升的创新方案
  • 如何快速修复Android设备认证:Play Integrity Fix完整使用指南
  • 【实验原理深度解析】弗兰克-赫兹实验:如何用电子碰撞“看见”原子的分立能级?
  • 2025_NIPS_FlowCut: Rethinking Redundancy via Information Flow for Efficient Vision-Language Models
  • 你的5G信号为啥总断?可能是波束追踪没跟上!聊聊手机和基站间的‘捉迷藏’
  • 联级阴影贴图CSM优化策略:分片权重与PCF算法实践
  • STM32标准外设库(标准库)2023最新下载指南与安装教程
  • 实战指南:在快马平台构建并部署一个基于Copaw的电商价格监控机器人
  • Intv_ai_mk11与STM32物联网项目结合:语音控制智能家居
  • Cursor Free VIP:AI编程助手永久授权的突破性解决方案
  • DriveStudio:如何在5分钟内开始你的城市3D场景重建项目?
  • AntiMicroX实战指南:5个场景让你轻松用手柄玩转所有PC游戏
  • 企业为什么不缺一个会写 SQL 的模型,而缺一个真正理解业务语义的系统?
  • 利用快马平台AI快速构建游戏cc switch功能原型,十分钟实现创意验证
  • bently 3500/90通讯网关
  • AI赋能临床科研:SupMed超超如何成为医生指尖上的智能助手
  • 患者选MRD检测,哪些机构更对口?
  • KK键盘 v3.9.4-解锁去广告版!
  • Java 线程池 vs Go GMP
  • 文墨共鸣大模型数据库课程设计助手:从ER图到SQL的智能生成
  • 新手入门指南:在快马平台用万文通思路打造你的第一个文本转换网页
  • 手把手教你搞定GD32F303的J-Link烧录:从驱动配置到避开0x08000000报错
  • SCL3300倾角传感器实战:从STM8S003驱动到数据解析全流程
  • 疗愈焦虑的音乐:科学选曲与放松技巧
  • Windows 10下Bindiff安装避坑全记录:从环境变量配置到i64文件比对实战
  • 如何用代码思维提升90%图表效率?揭秘Mermaid的可视化革命
  • 第四章:n8n 核心节点实战手册