直接new httpclient()会导致端口耗尽,因每个实例新建socket并滞留time_wait状态;ihttpclientfactory通过复用httpmessagehandler池解决该问题,按命名分组管理、自动轮换过期handler,并要求配置超时等策略必须在addhttpclient注册时设置而非对实例赋值。

别再每次请求都 new HttpClient(),也别把它当静态单例硬扛——端口耗尽、DNS 失效、连接卡死这些问题,90% 都出在这两个动作上。
为什么每次 new HttpClient() 会触发 SocketException: Address already in use
每次 new HttpClient() 都会新建一个底层 HttpMessageHandler,进而绑定新 socket。即使你加了 using 或调用 Dispose(),操作系统仍需数分钟回收该 socket(处于 TIME_WAIT 状态)。高并发下很快占满本地可用端口(Windows 实际可用远少于 65535)。
- 典型错误现象:
SocketException: Only one usage of each socket address is normally permitted、请求延迟陡增、偶发超时 - 它不是“内存泄漏”,是操作系统级资源耗尽,GC 完全不管
-
HttpClient本身线程安全,设计就是长期复用的——但复用 ≠ 静态单例
IHttpClientFactory 不是造 HttpClient 的,而是管 handler 的
IHttpClientFactory 的核心价值不在返回的 HttpClient 实例,而在它背后维护的 HttpMessageHandler 池。你每次调用 CreateClient() 得到的是轻量、短期对象,真正复用和健康检查的是 handler。
- 按命名分组复用 handler:
AddHttpClient("github")和AddHttpClient("payment")各自隔离配置与连接池 - 自动轮换过期 handler:DNS 变更后旧连接会被丢弃,新请求自动走新 IP
- 别手动
Dispose()工厂创建的 client——它由工厂托管释放,强行 Dispose 可能破坏 handler 复用 - 超时、重试、日志等策略必须配在 handler 层,例如:
ConfigurePrimaryHttpMessageHandler或AddPolicyHandler(配合 Polly)
ConfigureHttpClient vs 直接赋值 Timeout 的区别
写 client.Timeout = TimeSpan.FromSeconds(10) 是无效的——因为工厂返回的 HttpClient 实例随时可能被丢弃,下次 CreateClient() 就是全新对象,你的设置白费。
- 正确位置:在
AddHttpClient()注册时用 lambda 配置:ConfigureHttpClient(c => c.Timeout = TimeSpan.FromSeconds(10)) - 更推荐把超时逻辑下沉到 handler 层,尤其当你用
AddPolicyHandler做重试时,超时必须和重试策略对齐,否则重试还没发起就超时了 - 默认
BaseAddress和DefaultRequestHeaders也必须在这里设,而不是在业务方法里反复加
类型化客户端比命名客户端更适合封装业务逻辑
命名客户端(如 AddHttpClient("github"))适合配置差异大的外部服务;类型化客户端则把 HTTP 调用直接绑定到 C# 类,天然支持构造函数注入、单元测试 Mock,且生命周期由 DI 容器统一管理。
- 注册方式:
AddHttpClient<githubservice>()</githubservice>,内部自动注入HttpClient - 避免在
GithubService里再存一个static HttpClient或自己 new 实例 - 如果多个服务共用同一 BaseAddress 和认证逻辑,可提取基类 +
DelegatingHandler统一处理 Token 刷新
真正容易被忽略的点是:handler 的健康检查机制只在请求失败时触发,正常流量下不会主动探测连接有效性。如果你的服务会长时间空闲(比如后台定时任务),得自己加心跳或配置 MaxConnectionsPerServer 防连接僵死。











