最近生产上偶现Connection reset,记录下解决过程
在TCP首部中有6个标志位,其中一个标志位为RST,用于“复位”的。无论何时一个报文 段发往基准的连接( referenced connection)出现错误,TCP都会发出一个复位报文段。如果双方需要继续建立连接,那么需要重新进行三次握手建立连接。
导致“Connection reset”的原因是服务器端因为某种原因关闭了Connection,而客户端依然在读写数据,此时服务器会返回复位标志“RST”,然后此时客户端就会提示“java.net.SocketException: Connection reset”
TCP建立连接时需要三次握手,在释放连接需要四次挥手;例如三次握手的过程如下:
可以看到握手时会在客户端和服务器之间传递一些TCP头信息,比如ACK标志、SYN标志以及挥手时的FIN标志等。
除了以上这些常见的标志头信息,还有另外一些标志头信息,比如推标志PSH、复位标志RST等。其中复位标志RST的作用就是“复位相应的TCP连接”。
导致此异常的原因,总结下来有三种情况:
1.服务器端偶尔出现了异常,导致连接关闭
解决方法:采用出错重试机制
2.服务器端和客户端使用的连接方式不一致
解决方法:服务器端和客户端使用相同的连接方式,即同时使用长连接或短连接
3.如果是HTTPS,那么还存在TLS版本不一致
解决方法:服务器端和客户端使用相同的TLS版本
我们这边的问题是:协议不一致导致的连接问题,网站支持的是TLSv1.1 和 TLSv1.2, jdk1.7只支持TLSv1,且Java 7 没有支持 AES GCM 加密。
方法一:如果客户端JDK是1.7,服务器端要求TLSv1.2,那么在启动参数加上-Dhttps.protocols=TLSv1.2即可。
方法二:代码指定TLS版本 System.setProperty(“https.protocols”, “TLSv1.2”);
public class HttpClientFactory {
private static CloseableHttpClient client;
public static HttpClient getHttpsClient() throws Exception {
if (client != null) {
return client;
}
SSLContext sslcontext = SSLContexts.custom().useSSL().build();
sslcontext.init(null, new X509TrustManager[]{new HttpsTrustManager()}, new SecureRandom());
SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslcontext,new String[] { "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" }, null,
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
client = HttpClients.custom().setSSLSocketFactory(factory).build();
return client;
}
public static void releaseInstance() {
client = null;
}
}
public class HttpsTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}
调用方式如下:
HttpClient httpClient = HttpClientFactory.getHttpsClient();
HttpPost request = new HttpPost(requestUrl);
request.setEntity(new StringEntity(gson.toJson(requestMap), "application/json", "UTF-8"));
HttpResponse httpResponse = httpClient.execute(request);
resultStr = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
System.out.println(resultStr);
httpResponse.getEntity().getContent().close();