基于BackgroundService的Windows Service调用HttpClient.Send失败排查
用Worker模板创建的Windows服务本身运行正常,但调用HttpClient.Send()发送请求时会出现异常,注释该行代码后服务恢复正常。在Visual Studio中调试运行时,请求能正常发送且可被Fiddler捕获;但将服务发布并注册为Windows服务后,请求无法被Fiddler捕获,事件查看器中抛出SocketException(10060) 连接超时异常。
异常详情
类别:CorewsWorkerService.WindowsBackgroundService
事件ID:0
连接尝试失败,因为连接方在一段时间后未正确响应,或已建立的连接失败,因为连接主机未能响应。(SECRET:443)
异常:
System.Net.Http.HttpRequestException: 连接尝试失败,因为连接方在一段时间后未正确响应,或已建立的连接失败,因为连接主机未能响应。(SECRET:443)
---> System.Net.Sockets.SocketException (10060): 连接尝试失败,因为连接方在一段时间后未正确响应,或已建立的连接失败,因为连接主机未能响应。
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
at ......
相关代码
public class TotalArchiveService { public string CallService() { //SendRequest().GetAwaiter().GetResult(); SendRequest(); return "OK - " + DateTime.Now.ToString(); } //static async Task SendRequest() private void SendRequest() { try { using (var client = new HttpClient {}) { var endpointUrlCustomer = "https://SECRET"; // Create HTTP request. var customerBuilder = new UriBuilder(new Uri($"{endpointUrlCustomer}")); var customerRequest = new HttpRequestMessage(HttpMethod.Post, customerBuilder.Uri); // set SOAP action customerRequest.Headers.Add("SOAPAction", ""); // set SOAP body content string xmlSOAP = GetXmlSOAP(); customerRequest.Content = new StringContent(xmlSOAP, Encoding.UTF8, "text/xml"); // Add signature headers to request object SignHttpRequestMessage(customerRequest); // Perform request var customerResponse = client.Send(customerRequest); } } catch (HttpRequestException e) { throw e; } } //static async Task SignHttpRequestMessage(HttpRequestMessage request) private void SignHttpRequestMessage(HttpRequestMessage request) { // externalize configuration of these string certPath = ""; string keyStorePassword = ""; string keyId = ""; certPath = Path.GetFullPath(@"c:\cert2\cert.pfx"); // path to keystorefile keyStorePassword = "SECRET"; keyId = "SECRET"; X509Certificate2Collection certs = new X509Certificate2Collection(); X509Certificate2 cert = new X509Certificate2(certPath, keyStorePassword, X509KeyStorageFlags.DefaultKeySet | X509KeyStorageFlags.Exportable); var services = new ServiceCollection() .AddHttpMessageSigning() .UseKeyId(keyId) .UseSignatureAlgorithm(SignatureAlgorithm.CreateForSigning(cert, HashAlgorithmName.SHA256)) .UseDigestAlgorithm(HashAlgorithmName.SHA256) .UseUseDeprecatedAlgorithmParameter() .UseNonce(false) .UseHeaders() .Services; using (var serviceProvider = services.BuildServiceProvider()) { using (var signerFactory = serviceProvider.GetRequiredService<IRequestSignerFactory>()) { var requestSigner = signerFactory.CreateFor(keyId); //await requestSigner.Sign(request); requestSigner.Sign(request); } } request.Headers.Add("Signature", request.Headers.Authorization.ToString()); request.Headers.Authorization = null; } static string GetXmlSOAP() { string xmlSOAP = ""; xmlSOAP = @"<?xml version=""1.0"" encoding=""utf-8""?> <soapenv:Envelope xmlns:soapenv=""http://schemas.xmlsoap.org/soap/envelope/"" xmlns:wsc=""http://edb.com/ws/WSCommon_v22"" xmlns:urn=""urn:srv.cus.corews.enterprise.fs.evry.com:ws:customer:v20_1"" xmlns:urn1=""urn:srv.cus.corews.enterprise.fs.evry.com:domain:customer:v20_1"" xmlns:urn2=""urn:corews.enterprise.fs.evry.com:domain:common:v8""> <soapenv:Header> <wsc:AutHeader> <wsc:SourceApplication>SECRET</wsc:SourceApplication> <wsc:DestinationApplication>SECRET</wsc:DestinationApplication> <wsc:Function>SECRET</wsc:Function> <wsc:Version>20_1</wsc:Version> <wsc:ClientContext> <wsc:userid>SECRET</wsc:userid> <wsc:credentials/> <wsc:channel>BRA</wsc:channel> <wsc:orgid>9057</wsc:orgid> <!--Optional:--> <wsc:orgunit>36000</wsc:orgunit> <!--Optional:--> <wsc:customerid>SECRET</wsc:customerid> <!--Optional:--> <wsc:locale>no_NO</wsc:locale> <wsc:ip>127.0.0.1</wsc:ip> </wsc:ClientContext> </wsc:AutHeader> </soapenv:Header> <soapenv:Body> <urn:customerReadRequest> <urn:readQualification> <urn1:internationalCustomerKey> <urn2:internationalCustomerNumber>SECRET</urn2:internationalCustomerNumber> </urn1:internationalCustomerKey> </urn:readQualification> </urn:customerReadRequest> </soapenv:Body> </soapenv:Envelope>"; return xmlSOAP; } }
解决方案
1. 调整Windows服务运行权限
Windows服务默认以Local System账户运行,该账户可能没有外部网络访问权限,或无法读取证书文件:
- 打开服务管理器,找到目标服务,右键选择「属性」→「登录」,切换到具备网络访问权限的账户(如域账户、本地管理员),并确保该账户能读取
c:\cert2\cert.pfx文件。
2. 优化HttpClient使用方式
- 复用HttpClient实例:每次请求创建新
HttpClient会导致Socket资源耗尽,建议将HttpClient注册为单例或静态实例,避免频繁创建销毁。 - 改用异步调用:同步调用易导致线程阻塞,在服务环境下更易触发超时,恢复异步实现并保证调用链全程异步:
public async Task<string> CallService() { await SendRequest(); return "OK - " + DateTime.Now.ToString(); } private async Task SendRequest() { try { // 使用复用的HttpClient实例 var customerResponse = await client.SendAsync(customerRequest); customerResponse.EnsureSuccessStatusCode(); // 确保请求成功 } catch (HttpRequestException e) { // 记录日志后再抛出,避免服务直接崩溃 throw; } } private async Task SignHttpRequestMessage(HttpRequestMessage request) { // ... 其余代码不变 await requestSigner.Sign(request); // ... 其余代码不变 }
3. 配置代理
调试时Fiddler代理生效,但服务运行时不会自动使用代理,若目标服务器需通过代理访问,需在代码中配置:
var handler = new HttpClientHandler { Proxy = new WebProxy("http://你的代理地址:端口"), UseProxy = true }; using (var client = new HttpClient(handler)) { // ... 请求代码 }
或确保服务运行账户的系统代理设置正确。
4. 修复证书访问权限
- 右键证书文件
c:\cert2\cert.pfx,选择「属性」→「安全」,添加Local System账户并授予「读取」权限。 - 加载证书时使用
X509KeyStorageFlags.MachineKeySet,将证书加载到机器密钥容器,规避用户权限问题:X509Certificate2 cert = new X509Certificate2(certPath, keyStorePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
5. 增加日志排查
在SendRequest()方法中添加详细日志,记录请求头、证书加载状态、异常完整堆栈等信息,精准定位问题点。
内容的提问来源于stack exchange,提问作者Lars Moe

