连接重置-Java(基础套接字状态保持建立状态)

时间:2019-05-31 06:34:54

标签: java apache sockets apache-httpclient-4.x windows-server-2016

我正在调试连接重置的一个问题,需要一些帮助。

这是背景

使用Java版本8,Apache httpClient 4.5.2

我有一个以下程序,该程序可在Windows 10、7上成功运行,但最终会在Windows Server 2016上重置连接。

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;





public class TestConnectionReset
{
  static PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
  static  {
    connManager.setMaxTotal(10);
    connManager.setDefaultMaxPerRoute(2);
  }
  public static void main(String[] args) throws ClientProtocolException, IOException, InterruptedException {
    while (true) {
      HttpClientBuilder clientBuilder = HttpClientBuilder.create();
      RequestConfig config = RequestConfig.custom().setConnectTimeout(1800000).setConnectionRequestTimeout(1800000)
        .setSocketTimeout(1800000).build();
      clientBuilder.setDefaultRequestConfig(config);
      clientBuilder.setConnectionManager(connManager);
      String userName = "xxxxx";
      String password = "xxxxx";
      String userNamePasswordPair = String.valueOf(userName) + ":" + password;

      String authenticationData = "Basic " + new String((new Base64()).encode(userNamePasswordPair.getBytes()));

      HttpPost post = new HttpPost("https://url/rest/oauth/token");
      Map<String, String> requestBodyMap = new HashMap<String, String>();
      requestBodyMap.put("grant_type", "client_credentials");

      String req = getFormUrlEncodedBodyFromMap(requestBodyMap);

      StringEntity stringEntity = new StringEntity(req);
      post.setEntity(stringEntity);
      post.setHeader("Authorization", authenticationData);
      post.setHeader("Content-Type", "application/x-www-form-urlencoded");

      CloseableHttpClient closeableHttpClient = clientBuilder.build();
      HttpResponse response = closeableHttpClient.execute(post);
      Header[] hs = response.getAllHeaders();
      for (Header header : hs) {
        System.out.println(header.toString());
    }
      System.out.println(EntityUtils.toString(response.getEntity()));
      Thread.sleep(10*60*1000L);
    } 
  }


  public static String getFormUrlEncodedBodyFromMap(Map<String, String> formData) {
    StringBuilder requestBody = new StringBuilder();
    Iterator<Map.Entry<String, String>> itrFormData = formData.entrySet().iterator();
    while (itrFormData.hasNext()) {
      Map.Entry<?, ?> entry = (Map.Entry)itrFormData.next();
      requestBody.append(entry.getKey()).append("=").append(entry.getValue());
      if (itrFormData.hasNext()) {
        requestBody.append("&");
      }
    } 
    return requestBody.toString();
  }
}

我正在使用池化httpclient连接管理器。第一个时间循环执行中的第一个请求成功,但随后的下一个请求for循环的后续迭代失败。

我的发现

如果我们在Windows 10上看到底层套接字连接,则在第一个请求套接字进入CLOSE_WAIT状态后,下一个请求将在关闭现有连接并创建新连接的情况下执行。

实际上,服务器会在5分钟的时间内关闭连接。但是Windows 10能够检测到它并在触发下一个请求时重新启动连接。

现在,在Windows Server 2016上,我可以看到netstat显示套接字已建立状态。意味着连接已准备就绪,可以使用该连接,并且服务器已关闭该连接,因此导致连接重置错误。

我怀疑这是一个环境问题,即使服务器终止后,服务器2016仍保持套接字建立,但在Windows 10上套接字状态已更改为CLOSE_WAIT。

对此有所帮助

1 个答案:

答案 0 :(得分:1)

最终找到了根本原因,

它与Microsoft azure有关。他们使用SNAT并在4分钟的空闲时间后关闭出站TCP连接。这浪费了我5天的时间去弄清楚。

表示您是否通过保持活动状态与服务器连接,并希望您可以重用连接,直到服务器超时并发送FIN。但是在此之前,如果闲置时间达到4分钟,天蓝色就会杀死它。繁荣!!。最糟糕的是,它甚至没有通过RST通知服务器或客户端,这意味着违反TCP并质疑其可靠性。

 clientBuilder.setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {

        @Override
        public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
            // TODO Auto-generated method stub
            return 3*60*1000;
        }
    });

使用上面的代码,我设法在3分钟到期时关闭连接,并在天蓝色杀死它之前将其关闭。