HttpClient 连接状态在使用一段时间后保持在 TIME_WAIT 导致 Socket Exception

Posted

技术标签:

【中文标题】HttpClient 连接状态在使用一段时间后保持在 TIME_WAIT 导致 Socket Exception【英文标题】:HttpClient connection state remains in TIME_WAIT after use for a while causing Socket Exception 【发布时间】:2019-05-04 14:03:27 【问题描述】:

我们正在使用HttpClient 访问托管在另一台服务器上的WebAPI。 我们有一个 MVC5 应用程序,它使用 IIS 8.5 从 WebAPI window server 2012 R2 获取数据

以下是用于从 webapi 获取数据的代码

//creating static instance of http client
private static HttpClient client = new HttpClient();

//static method to add common header
static ApiCall()

   client.DefaultRequestHeaders.Add("Authorization", 
                 string.Format("Value 0", AppSettings.ApiSecurityKey));
   client.DefaultRequestHeaders.ConnectionClose = true;


//finally posting data to the api
//objprop contains the data and other setting
string url = "apiurl";
var postContent = new StringContent(objprop.DataForPost, 
      System.Text.Encoding.UTF8, objprop.ContentType);
response = client.PostAsync(url, postContent).Result;

但是在 PT 测试期间,我收到了多个处于 TIME_WAIT 状态的请求。因此,我们得到了套接字异常。

使用 HttpClient 的最佳实践是什么?

有没有什么设置可以在使用后释放socket。

类似的问题在这里 1.How do I prevent Socket/Port Exhaustion? 2.Why does HttpClient leave sockets open? 但这对我没有帮助。

编辑 1 更多细节

:InnerException- System.Net.Http.HttpRequestException:发送请求时出错。 ---> System.Net.WebException:无法连接到远程服务器 ---> System.Net.Sockets.SocketException:每个套接字地址(协议/网络地址/端口)通常只允许使用一次 x.x.x:80 在 System.Net.Sockets.Socket.InternalEndConnect(IAsyncResult asyncResult) 在 System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult) 在 System.Net.ServicePoint.ConnectSocketInternal(布尔连接失败,套接字 s4,套接字 s6,套接字和套接字,IPAddress 和地址,ConnectSocketState 状态,IAsyncResult asyncResult,异常和异常) --- 内部异常堆栈跟踪结束 --- 在 System.Net.HttpWebRequest.EndGetRequestStream(IAsyncResult asyncResult,TransportContext& 上下文) 在 System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar) --- 内部异常堆栈跟踪结束 --- : URL- http://x.x.x.com//api/GetMenu

【问题讨论】:

blog.stephencleary.com/2012/07/dont-block-on-async-code.html @शेखर 该链接如何提倡 HttpClient 的多个实例?这是因为您正在使用 .Result 阻止异步代码,并且您可能正在等待对该代码的调用,尽管您尚未在上下文中显示它。 @Crowcoder 我确实浏览了整个代码,对于评论感到抱歉。我正在调查。 @Crowcoder 你认为这是导致 TIME_WAIT 状态的原因吗? 我不是网络工程师,但如果你没有死锁,那么我不会担心它,尽管你应该重构为不需要因其他原因阻塞代码。 HttpClient 将重用连接,这是首先使用共享静态实例的原因,所以我相信这是设计使然。 【参考方案1】:

您对HttpClient 的用法是正确的;尽管正如一些人所评论的那样,最好一直使用async,但这不是您错误的原因。

TIME_WAIT 是 TCP/IP 连接的强制阶段;一旦建立连接,发起连接关闭的一方将进入此状态一段预先配置的时间。

TCP/IP 有一个有限的端口池用于进出连接。

新连接使用其中的一个子集(也称为动态或临时端口范围)在每一侧分配一个端口。

您的应用在尝试创建新的传出连接时出现错误“正在使用的地址”,但 TCP 已用完动态范围内的本地端口。

根据启动连接关闭的一端,可能是您的客户端或服务器端口耗尽。

This article from Microsoft 详细介绍了如何修改 Windows TCP/IP 设置以实现高连接率:

TIME_WAIT 周期从 120 秒更改为 30 秒 将动态端口数从 16384 增加到 64511

同时执行这两项操作会将最大连接速率从每秒 136 个连接增加到大约 2150 个。

【讨论】:

【参考方案2】:

经过多次检查,使用wireshark 和telnet -n 5,我发现了一种有效的方法来降低httpClient 的套接字使用率是编写我自己的POST 套接字方法。

    private void PostSocket(MyObject o)
    
        string host = "localhost";
        int port = 9876;
        
        IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
        IPEndPoint remoteEP = new IPEndPoint(ipAddress, port);

        // Create a TCP socket
        Socket sock = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        sock.Connect(remoteEP);

        // The ToJson uses Newtonsoft method
        var data = new StringContent(o.ToJson(o), Encoding.UTF8, "application/json");
        byte[] byteData = data.ReadAsByteArrayAsync().Result;

        var post = $"POST /prices HTTP/1.1" + Environment.NewLine;
        post += "Content-Type: application/json" + Environment.NewLine;
        post += "Host: " + host + ":" + port + Environment.NewLine;
        post += "Content-Length: " + byteData.Length + Environment.NewLine;
        // This extra newline is required
        post += Environment.NewLine;
        byte[] postData = Encoding.UTF8.GetBytes(post);

        byte[] sendData = new byte[byteData.Length + postData.Length];
        Array.Copy(postData, 0, sendData, 0, postData.Length);
        Array.Copy(byteData, 0, sendData, postData.Length, byteData.Length);

        // Minimum socket send timeout
        sock.SendTimeout = 500;
        sock.Send(sendData);
        
        // If this is not set to false it hangs
        sock.Disconnect(false);
    

【讨论】:

以上是关于HttpClient 连接状态在使用一段时间后保持在 TIME_WAIT 导致 Socket Exception的主要内容,如果未能解决你的问题,请参考以下文章

解决httpclient因为保持永久长连接造成连接吊死的问题

SSH 保持连接

连接保持活动在某些主机上无法与 System.Net.Http.HttpClient 一起使用

HttpClient连接池

HttpClient连接池的连接保持超时和失效机制

HttpClient连接池的连接保持超时和失效机制