Сброс подключения — Java (состояние базового сокета остается установленным) Azure VM

Я отлаживаю одну проблему сброса соединения и нуждаюсь в помощи.

Вот фон

Используя Java версии 8, apache httpClient 4.5.2

У меня есть следующая программа, которая успешно работает в Windows 10, 7, но заканчивается сбросом соединения на виртуальной машине Azure 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 для пула. 1-й запрос в 1-м цикле выполнения выполнен успешно, но последующая итерация цикла for со следующим запросом завершается неудачно.

Мои выводы

Если мы видим базовое соединение сокета в Windows 10, после первого запроса сокет переходит в состояние CLOSE_WAIT, а следующий запрос выполняется с закрытием существующего соединения и созданием нового соединения.

Фактически сервер закрывает соединение в течение 5 минут. Но Windows 10 может обнаружить это и повторно инициировать соединение при запуске следующего запроса.

Теперь, на Windows Server 2016, я вижу, что netstat показывает состояние сокета ESTABLISHED. Означает, что соединение готово к использованию, и при этом оно устанавливает то же соединение, и, наконец, сервер уже закрыл его, что приводит к ошибке сброса соединения.

Я подозреваю, что это проблема окружающей среды, когда сервер 2016 сохраняет сокет ESTABLISHED даже после того, как сервер его завершил, но в Windows 10 статус сокета изменился на CLOSE_WAIT.

Помощь в этом очень ценится

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
790
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Наконец-то нашел первопричину,

Проблема с Microsoft Azure. Они используют SNAT и закрывают исходящие TCP-соединения после 4-минутного простоя. Это потратило впустую мои 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 минут и закрыть его до того, как лазурь убьет его.

Другие вопросы по теме