Я отлаживаю одну проблему сброса соединения и нуждаюсь в помощи.
Вот фон
Используя 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.
Помощь в этом очень ценится
Наконец-то нашел первопричину,
Проблема с 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 минут и закрыть его до того, как лазурь убьет его.