C# HttpClient 一个现有的连接被远程主机强行关闭

Posted

技术标签:

【中文标题】C# HttpClient 一个现有的连接被远程主机强行关闭【英文标题】:C# HttpClient An existing connection was forcibly closed by the remote host 【发布时间】:2017-09-14 15:29:25 【问题描述】:

我正在使用他们的hosted page integration 与 Alternative Payments 进行集成。他们的 C# SDK 目前没有这种集成,但正如您所见,它非常简单,我创建了一个小类来发送 post 请求并获取 JSON 响应。

我测试了我在 PostMan 和 cURL 上发送的 json 对象,它们都可以工作,还有身份验证标头,所以我认为它们不是问题。这是我的类的构造函数:

public AlternativePaymentsCli(string apiSecretKey)

    this._apiSecretKey = apiSecretKey;

    _httpClient = new HttpClient();
    _httpClient.DefaultRequestHeaders.Accept
        .Add(new MediaTypeWithQualityHeaderValue("application/json"));

    var authInfo = _apiSecretKey;
    authInfo = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("0:", _apiSecretKey)));

    // The two line below because I saw in an answer on ***.
    _httpClient.DefaultRequestHeaders.Add("Connection", "Keep-Alive"); 
    _httpClient.DefaultRequestHeaders.Add("Keep-Alive", "3600");

    _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Anything.com custom client v1.0");
    _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authInfo);


以及我发布数据的方法:

public string CreateHostedPageTransaction(HostedPageRequest req) 

    var settings = new JsonSerializerSettings  NullValueHandling = NullValueHandling.Ignore ;

    // I send this same json content on PostMan and it works. The json is not the problem
    var content = new StringContent(JsonConvert.SerializeObject(req, settings), Encoding.UTF8, "application/json");
    var response = _httpClient.PostAsync(this._baseUrl + "/transactions/hosted", content).Result;
    var responseText = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();

    if (response.IsSuccessStatusCode)
        return responseText;

    return "";

然后我在 PostAsync 行收到此错误:An existing connection was forcibly closed by the remote host。这是错误详情:

[SocketException (0x2746): An existing connection was forcibly closed by the remote host]
   System.Net.Sockets.Socket.EndReceive(IAsyncResult asyncResult) +8192811
   System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult) +47

[IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.]
   System.Net.TlsStream.EndWrite(IAsyncResult asyncResult) +294
   System.Net.ConnectStream.WriteHeadersCallback(IAsyncResult ar) +149

[WebException: The underlying connection was closed: An unexpected error occurred on a send.]
   System.Net.HttpWebRequest.EndGetRequestStream(IAsyncResult asyncResult, TransportContext& context) +324
   System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar) +137

[HttpRequestException: An error occurred while sending the request.]

我正在使用 C# 4.5,Asp.Net MVC。我一直在阅读相同错误的答案,但到目前为止,他们都没有解决我的问题。我在这段代码中遗漏了什么?

感谢您的帮助

【问题讨论】:

只是一个很好的提示,使用var 做任何事情都会让你的同事讨厌你。仅当类型明显(即var date = new DateTime();)时才使用var,其非常显然是DateTime。但是var response = _httpClient.PostAsync(this._baseUrl + "/transactions/hosted", content).Result; 不清楚,因为.Result 是一个属性,而不是隐含它是什么类型。 @maccettura 相反,使用var 使代码更清晰。除非您编写非常长的方法,否则类型是什么是没有歧义的,在这种情况下,您应该真的,真的将它们分开。如果你编写的方法太长以至于看不到类型是什么,同事会讨厌你 @PanagiotisKanavos var 仅在 隐含类型 时使代码更清晰,在任何地方使用 var(即使在不隐含类型的情况下)只是 糟糕的编程. @maccettura 至于.Result,同事会讨厌任何用.Wait().Result 阻止异步调用的人。无论如何,返回类型是已知的。它是 HttpResponseMessage。看到名称对您没有帮助,因为无论如何您都必须使用智能感知来查找要使用的方法 @maccettura 例如,这就是为什么函数式语言使用类型推断而不是明确指定类型的原因 你可能不熟悉类型推断。这并没有使它成为糟糕的编程。另一方面,打电话给.Result 应该引起任何人的不满 【参考方案1】:

我没有在您的代码示例中看到您设置 _baseUrl 的值,但我假设这是在某处完成的。我还假设由于这与付款有关,因此 URL 是 HTTPS。如果远程主机已禁用 TLS 1.0 并且您的连接以 TLS 1.0 的形式出现,则可能会导致该行为。我知道 C# 4.6 默认启用 TLS 1.0/1.1/1.2 支持,但我认为即使支持 TLS 1.1 和 1.2,C# 4.6 仍然默认仅支持 SSL3/TLS 1.0。如果这是导致问题的原因,您可以使用以下代码手动将 TLS 1.1 和 1.2 添加到启用的值。

System.Net.ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

【讨论】:

非常感谢先生,您解决了我的问题。我在本地运行了一个虚假的 HTTPS,chrome 警告我该网站不安全等等。一定是这样。 您无需更改代码即可为当前在 prod 中运行的应用程序启用此功能:docs.microsoft.com/en-us/officeonlineserver/… 是的,这仅在应用程序域中需要(在工作角色中使用)还是我需要根据 HttpClient 请求进行设置? @Ruchira 和 NathanTregillus 您可以为每个请求设置它(在发送请求之前添加代码),或者您可以通过在 Global.asax 中设置 Application_Start() 为整个应用程序设置一次。 cs 谢谢@PaulPearce。我最终将它添加到 Global.asax。使用“使用 System.Net;”添加顶部以支持 ServicePointManager 和 SecurityProtocolType。【参考方案2】:

如果您使用的是 .Net 4.0,则未定义 SecurityProtocolType.Tls11 和 SecurityProtocolType.Tls2,因此您可以使用下面的硬编码值。

ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;

【讨论】:

【参考方案3】:

可以在不更改代码的情况下解决问题,如this excellent answer 中描述的类似问题:

将 web 项目重新定位到 .Net 4.6+,然后更新 web.config 如下:

<system.web>
  <compilation targetFramework="4.6" /> 
  <httpRuntime targetFramework="4.6" /> 
</system.web>

【讨论】:

【参考方案4】:

这对我有用,第一行确保协议 ssl3TLS1.2,第二行忽略任何潜在的证书错误(忽略并继续 - 就像过期的证书。):

ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback +=  (sender, certificate, chain, sslPolicyErrors) => true;

【讨论】:

您好,欢迎来到社区,尝试使用文本编辑器中的格式化工具使您的答案看起来更好,更易于阅读。代码格式化程序特别有用,它会创造奇迹。 这不应被视为生产解决方案。忽略证书检查会使 SSL 的安全性无效。这在测试环境中很好。【参考方案5】:

对我来说,这个错误是由于忘记配置代理服务器造成的。 使用以下代码构造 HttpClient 为我的 .NET 4.7.2 应用程序解决了这个问题:

var httpClientHandler = new HttpClientHandler  Proxy = WebRequest.GetSystemWebProxy() ;
var httpClient = new HttpClient(httpClientHandler);

希望这对某人有所帮助。

【讨论】:

以上是关于C# HttpClient 一个现有的连接被远程主机强行关闭的主要内容,如果未能解决你的问题,请参考以下文章

C# UDP 一个现有的连接被远程主机强行关闭

UDP Socket:一个现有的连接被远程主机强行关闭

Windows下的UDP爆了10054--远程主机强迫关闭了一个现有的连接

java.io.IOException: 远程主机强迫关闭了一个现有的连接。

python 远程主机强迫关闭了一个现有的连接 socket 超时设置 errno 10054

SQL连接数据库时调试出现“远程主机强迫关闭了一个现有的连接”