我们在更换服务器或者转为https的时候,在进行请求时产生异常
javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
W/System.err: at com.android.org.conscrypt.SSLNullSession.getPeerCertificates(SSLNullSession.java:104)
W/System.err: at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:98)
W/System.err: at org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:393)
W/System.err: at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:189)
W/System.err: at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:169)
W/System.err: at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:124)
W/System.err: at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:365)
W/System.err: at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:608)
W/System.err: at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:522)
W/System.err: at net.tsz.afinal.http.HttpHandler.makeRequestWithRetries(HttpHandler.java:79)
W/System.err: at net.tsz.afinal.http.HttpHandler.doInBackground(HttpHandler.java:115)
W/System.err: at net.tsz.afinal.core.AsyncTask$2.call(AsyncTask.java:145)
W/System.err: at java.util.concurrent.FutureTask.run(FutureTask.java:237)
W/System.err: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
W/System.err: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
W/System.err: at java.lang.Thread.run(Thread.java:818)
这个时候查了好几次,找不到原因,好蛋疼。。。。
后来上谷歌官网查询
产生原因一:
Protocol Supported (API Levels) Enabled by default (API Levels)
SSLv3 1+ 1+
TLSv1 1+ 1+
TLSv1.1 16+ 20+
TLSv1.2 16+ 20+
在不同的Android版本中,对应的TLS 版本不同,这时候,需要我们去让后台查询服务器,看是否支持,如果支持,在查看本地代码。
解决方法 一:
public class SSL extends SSLSocketFactory {
private SSLSocketFactory defaultFactory;
// Android 5.0+ (API level21) provides reasonable default settings
// but it still allows SSLv3
// https://developer.android.com/about/versions/android-5.0-changes.html#ssl
static String protocols[] = null, cipherSuites[] = null;
static {
try {
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket();
if (socket != null) {
/* set reasonable protocol versions */
// - enable all supported protocols (enables TLSv1.1 and TLSv1.2 on Android <5.0)
// - remove all SSL versions (especially SSLv3) because they‘re insecure now
List protocols = new LinkedList<>();
for (String protocol : socket.getSupportedProtocols())
if (!protocol.toUpperCase().contains("SSL"))
protocols.add(protocol);
SSL.protocols = protocols.toArray(new String[protocols.size()]);
/* set up reasonable cipher suites */
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// choose known secure cipher suites
List allowedCiphers = Arrays.asList(
// TLS 1.2
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECHDE_RSA_WITH_AES_128_GCM_SHA256",
// maximum interoperability
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA",
// additionally
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA");
List availableCiphers = Arrays.asList(socket.getSupportedCipherSuites());
// take all allowed ciphers that are available and put them into preferredCiphers
HashSet preferredCiphers = new HashSet<>(allowedCiphers);
preferredCiphers.retainAll(availableCiphers);
/* For maximum security, preferredCiphers should *replace* enabled ciphers (thus disabling
* ciphers which are enabled by default, but have become unsecure), but I guess for
* the security level of DAVdroid and maximum compatibility, disabling of insecure
* ciphers should be a server-side task */
// add preferred ciphers to enabled ciphers
HashSet enabledCiphers = preferredCiphers;
enabledCiphers.addAll(new HashSet<>(Arrays.asList(socket.getEnabledCipherSuites())));
SSL.cipherSuites = enabledCiphers.toArray(new String[enabledCiphers.size()]);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public SSL(X509TrustManager tm) {
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, (tm != null) ? new X509TrustManager[]{tm} : null, null);
defaultFactory = sslContext.getSocketFactory();
} catch (GeneralSecurityException e) {
throw new AssertionError(); // The system has no TLS. Just give up.
}
}
private void upgradeTLS(SSLSocket ssl) {
// Android 5.0+ (API level21) provides reasonable default settings
// but it still allows SSLv3
// https://developer.android.com/about/versions/android-5.0-changes.html#ssl
if (protocols != null) {
ssl.setEnabledProtocols(protocols);
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && cipherSuites != null) {
ssl.setEnabledCipherSuites(cipherSuites);
}
}
@Override public String[] getDefaultCipherSuites() {
return cipherSuites;
}
@Override public String[] getSupportedCipherSuites() {
return cipherSuites;
}
@Override public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
Socket ssl = defaultFactory.createSocket(s, host, port, autoClose);
if (ssl instanceof SSLSocket)
upgradeTLS((SSLSocket) ssl);
return ssl;
}
@Override public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
Socket ssl = defaultFactory.createSocket(host, port);
if (ssl instanceof SSLSocket)
upgradeTLS((SSLSocket) ssl);
return ssl;
}
@Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
Socket ssl = defaultFactory.createSocket(host, port, localHost, localPort);
if (ssl instanceof SSLSocket)
upgradeTLS((SSLSocket) ssl);
return ssl;
}
@Override public Socket createSocket(InetAddress host, int port) throws IOException {
Socket ssl = defaultFactory.createSocket(host, port);
if (ssl instanceof SSLSocket)
upgradeTLS((SSLSocket) ssl);
return ssl;
}
@Override public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
Socket ssl = defaultFactory.createSocket(address, port, localAddress, localPort);
if (ssl instanceof SSLSocket)
upgradeTLS((SSLSocket) ssl);
return ssl;
}
}
然后我们只需要给我们的请求设置这个SSLSocketFactory就可以了,我们以okhttp为例,如下:
//定义一个信任所有证书的TrustManager
final X509TrustManager trustAllCert = new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
};
//设置OkHttpClient
OkHttpClient client = new OkHttpClient.Builder().sslSocketFactory(new SSL(trustAllCert), trustAllCert).build();
产生原因二:
从异常信息中,我们可以看出网络请求过程中的握手过程中,握手被中断了
TCP建立连接时需要三次握手,在释放连接需要四次挥手;例如三次握手的过程如下:
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;
第二次握手:服务器收到syn包,并会确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
可以看到握手时会在客户端和服务器之间传递一些TCP头信息,比如ACK标志、SYN标志以及挥手时的FIN标志等。
除了以上这些常见的标志头信息,还有另外一些标志头信息,比如推标志PSH、复位标志RST等。其中复位标志RST的作用就是“复位相应的TCP连接”。
另一个可能导致的“Connection reset”的原因是服务器设置了Socket.setLinger (true, 0)。但我检查过线上的tomcat配置,是没有使用该设置的,而且线上的服务器都使用了nginx进行反向代理,所以并不是该原因导致的。关于该原因上面的oracle文档也谈到了并给出了解释。
此外啰嗦一下,另外还有一种比较常见的错误“Connection reset by peer”,该错误和“Connection reset”是有区别的:
服务器返回了“RST”时,如果此时客户端正在从Socket套接字的输出流中读数据则会提示Connection reset”;
服务器返回了“RST”时,如果此时客户端正在往Socket套接字的输入流中写数据则会提示“Connection reset by peer”。
首先是出错了重试:这种方案可以简单防止“Connection reset”错误,然后如果服务不是“幂等”的则不能使用该方法;比如提交订单操作就不是幂等的,如果使用重试则可能造成重复提单。
然后是客户端和服务器统一使用TCP长连接:客户端使用TCP长连接很容易配置(直接设置HttpClient就好),而服务器配置长连接就比较麻烦了,就拿tomcat来说,需要设置tomcat的maxKeepAliveRequests、connectionTimeout等参数。另外如果使用了nginx进行反向代理或负载均衡,此时也需要配置nginx以支持长连接(nginx默认是对客户端使用长连接,对服务器使用短连接)。
使用长连接可以避免每次建立TCP连接的三次握手而节约一定的时间,但是我这边由于是内网,客户端和服务器的3次握手很快,大约只需1ms。ping一下大约0.93ms(一次往返);三次握手也是一次往返(第三次握手不用返回)。根据80/20原理,1ms可以忽略不计;又考虑到长连接的扩展性不如短连接好、修改nginx和tomcat的配置代价很大(所有后台服务都需要修改);所以这里并没有使用长连接。ping服务器的时间如下图:
最后的解决方案是客户端和服务器统一使用TCP短连接:我这边正是这么干的,而使用短连接既不用改nginx配置,也不用改tomcat配置,只需在使用HttpClient时使用http1.0协议并增加http请求的header信息(Connection: Close),源码如下:
httpGet.setProtocolVersion(HttpVersion.HTTP_1_0);
httpGet.addHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_CLOSE);
原因三:
理解这个问题,我试图找到连接背后的原因复位和我想出了以下原因:
在远程主机上的对等应用程序突然停止,重新启动主机,主机或远程网络接口被禁用,或远程主机使用硬关闭。
也可能导致这个错误,如果一个连接被打破,由于保持活动检测到故障,而一个或多个操作正在进行中。此时,正在进行的操作失败和后续操作将失败。Network dropped connection on reset(On Windows(WSAENETRESET))Connection reset by peer(On Windows(WSAECONNRESET))
如果目标服务器是由防火墙,这是真正在大多数情况下的保护,生存时间(TTL)或与该端口相关联的超时强行关闭空闲在给定的超时连接。这是我们关心的事
解析度:
在服务器端的事件,如突然停止服务,重新启动,禁用网络接口不能以任何方式处理。
在服务器端,配置防火墙与较高的生存时间(TTL)或超时值,如3600秒的指定端口。
客户可以“尝试”保持网络活跃,以避免或减少Connection reset by peer。
一般情况下要去的网络流量保持连接活着,问题/异常没有经常看到。的WiFi有至少机会Connection reset by peer。
与所述移动网络的2G,3G和4G,其中该分组数据传送是间歇的和依赖于移动网络的可用性,它可能无法重置在服务器侧和结果到的TTL计时器Connection reset by peer。
下面是项建议对各种论坛设置来解决问题
ConnectionTimeout:只有在建立连接超时使用。如果主机需要时间来连接这更高的价值,使客户端等待连接。
SoTimeout:插座超时它说在其内的数据分组被接收到考虑将该连接视为active.If没有在规定时间内接收到的数据的最大时间,连接被假定为停滞/断。
Linger:多达什么时候插座不应该当数据正在排队等待发送,并且关闭套接字函数被调用的插座上关闭。
TcpNoDelay:是否要禁用保存和积累的TCP数据包缓冲区,并送他们一旦达到阈值?设置为true,将跳过TCP缓冲,使每个请求立即发送。在网络中可以变慢通过增加由于更小和更频繁的分组传输的网络流量而引起的。
所以没有上述参数有助于保持网络活着,因而是无效的。
我发现一个设置,可以帮助解决这是这个函数的问题
setKeepAlive(true)
setSoKeepalive(HttpParams params, enableKeepalive=”true”)
我怎么解决我的问题?
设置 HttpConnectionParams.setSoKeepAlive(params, true)
抓住SSLException和检查的异常消息Connection reset by peer
如果发现异常,存储下载/阅读进度,并创建一个新的连接。
如果可能的话继续下载/阅读否则重新启动下载