IHttpClientFactory通过复用HttpMessageHandler池解决端口耗尽问题,避免每次new HttpClient()导致TIME_WAIT堆积;它按命名分组复用handler并自动轮换过期实例,客户端实例短期创建但底层handler长期复用、健康检查。

为什么直接 new HttpClient() 会耗尽端口
因为 HttpClient 是线程安全、设计为长期复用的对象,但很多人误把它当一次性工具——每次请求都 new HttpClient() 再 .Dispose(),导致底层 Socket 连接未及时释放,堆积在 TIME_WAIT 状态,几分钟内就可能占满本地端口(尤其高并发时)。Windows 默认 65535 个端口,实际可用远少于这个数。
常见错误现象:SocketException: Only one usage of each socket address is normally permitted 或响应变慢、超时陡增。
- 别在方法里
new HttpClient(),哪怕加了using - 别把
HttpClient声明为static字段手动单例——它不自动处理 DNS 变更和连接过期 - 别给每个 API 封装类配一个独立的
HttpClient实例(比如UserClient、OrderClient各持一个)
IHttpClientFactory 是怎么解决这个问题的
IHttpClientFactory 不是“造 HttpClient 的工厂”,而是管理 HttpClient 实例生命周期 + 复用底层 HttpMessageHandler 的协调器。它背后维护一个 HttpMessageHandler 池,按命名分组复用 handler,同时自动轮换过期 handler(比如 DNS 变更后重建连接)。
关键点:你拿到的 HttpClient 实例本身是短期的(每次 CreateClient() 都新对象),但它内部的 Handler 是共享、复用、带健康检查的。
- 注册方式必须在
Program.cs(.NET 6+)或Startup.ConfigureServices里调用services.AddHttpClient() - 命名客户端(如
AddHttpClient("github"))适合不同 BaseAddress / Handler 配置的场景 - 类型化客户端(
AddHttpClient<githubservice>()</githubservice>)适合封装业务逻辑,构造函数注入HttpClient - 不要自己
Dispose()工厂创建的HttpClient——它由工厂托管释放
配置超时、重试、日志这些事该写在哪
所有策略配置必须落在 HttpMessageHandler 层或工厂注册阶段,而不是对 HttpClient 实例赋值——因为实例随时被丢弃,改了也白改。
例如超时:不能写 client.Timeout = TimeSpan.FromSeconds(10),而应通过 ConfigurePrimaryHttpMessageHandler 或 AddPolicyHandler(需 Polly)来设。
- 超时统一在
AddHttpClient的 lambda 里配ConfigureHttpClient:设置BaseAddress和默认DefaultRequestHeaders - 重试、熔断等策略用
AddPolicyHandler(配合Polly),它作用于 handler 级别,随 handler 复用 - 日志靠
ILogger<httpclient></httpclient>自动注入,无需额外配置;想区分不同客户端日志?用命名客户端,日志分类名就是注册名 - 自定义
HttpMessageHandler(比如加证书校验)用ConfigurePrimaryHttpMessageHandler,别碰HttpClient本身
什么时候真该自己管 HttpClient
极少数场景下,工厂反而碍事:比如你要做细粒度连接控制(每个请求指定不同代理、禁用 Keep-Alive)、或集成非标准协议(HTTP/2 服务端推送)、或跑在资源极度受限的嵌入式环境(.NET nanoFramework)。
此时可手动复用 HttpMessageHandler,但绝不是 new HttpClient(new MyHandler()) 后扔进静态字段——得自己实现 handler 的池化、过期检测、DNS 刷新。
- 99% 的 WebAPI 调用、微服务间通信、定时拉取数据,都该用
IHttpClientFactory - 如果你在写 SDK 供别人用,暴露
HttpClient参数不如暴露HttpMessageHandler工厂回调 - 最易被忽略的一点:工厂注册的命名客户端,如果没被任何地方
GetService或构造注入过,对应 handler 永远不会被创建——懒加载没错,但别误以为“注册了就生效”


















