目錄
- 前言
- 一、UnknownHostException
- 1、網絡斷開驗證
- 2、DNS 服務器意外掛掉驗證
- 3、DNS 服務器故障驗證
- 4、所需診斷信息
- 二、ConnectTimeoutException
- 三、SocketTimeoutException
- 1、子錯誤 - 讀超時
- 2、子錯誤 - SSL 握手超時
- 3、子錯誤 - 未知原因
- 四、HttpHostConnectException
- 1、服務器故障驗證
- 2、代理服務器故障驗證
- 3、所需診斷信息
- 4、參考資料
- 五、NoRouteToHostException
- 六、SSLException
- 1、子錯誤 - SSL 讀期間連接重置
- 2、子錯誤 - SSL 握手期間連接重置
- 七、SSLHandshakeException
- 1、子錯誤 - 握手失敗
- 2、子錯誤 - 連接超時
- 3、子錯誤 - 連接重置
- 4、子錯誤 - 連接被關閉
- 八、SSLProtocolException
- 1、子錯誤 - 記錄MAC無效的讀失敗
- 2、子錯誤 - 協議版本導致的讀失敗
- 九、NoHttpResponseException
- 十、InterruptedIOException
- 1、子錯誤 - 連接已關閉
- 十一、IOException
- 1、子錯誤 - 連接關閉
- 2、子錯誤 - 請求被終止
- 十二、SocketException
- 1、子錯誤 - 讀消息連接重置
- 2、子錯誤 - 連接重置
- 3、子錯誤 - Socket 關閉
- 十三、ConnectException
- 1、子錯誤 - 連接被拒絕
- 2、子錯誤 - 主機不可達
- 3、子錯誤 - 網絡不可達
- 十四、ClientProtocolException
- 1、子錯誤 - 未知原因
前言
注:OkHttp 代碼基于 OkHttp 3.4 分析。Android 代碼基于 Android 6.0.0_r26 分析。
一、UnknownHostException
錯誤說明: 域名解析失敗。
錯誤消息: Unable to resolve host “m9.music.126.net”: No address associated with hostname
可能原因:
- 網絡斷開;
- DNS 服務器意外掛掉;
- DNS 服務器故障。
DNS 服務器掛掉或者故障這種問題比較少見,然而之前確實發生過大范圍的DNS 服務器問題。
針對這些原因,我們可以做一些模擬測試。
1、網絡斷開驗證
對于網絡連接斷開的情況,我們采用如下的方法來模擬測試:
- 關閉手機的 WiFi 和移動網絡,也就是使手機處于完全斷網的情況;
- 執行一個 HTTP 請求。
以 OkHttp 為例,在請求開始之后,立即就報出了如下的異常:
java.net.UnknownHostException: Unable to resolve host "www.wolfcstech.com": No address associated with hostnameat java.net.InetAddress.lookupHostByName(InetAddress.java:470)at java.net.InetAddress.getAllByNameImpl(InetAddress.java:252)at java.net.InetAddress.getAllByName(InetAddress.java:215)at okhttp3.Dns$1.lookup(Dns.java:39)at com.netease.netlib.OkHttp3Utils$MyDns.lookup(OkHttp3Utils.java:45)at okhttp3.internal.connection.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:170)at okhttp3.internal.connection.RouteSelector.nextProxy(RouteSelector.java:136)at okhttp3.internal.connection.RouteSelector.next(RouteSelector.java:81)at okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:171)at okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:121)at okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:100)at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:120)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at com.netease.netlib.OkHttp3Utils$MyInterceptor.intercept(OkHttp3Utils.java:29)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:179)at okhttp3.RealCall$AsyncCall.execute(RealCall.java:129)at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)at java.lang.Thread.run(Thread.java:833)
Caused by: android.system.GaiException: android_getaddrinfo failed: EAI_NODATA (No address associated with hostname)at libcore.io.Posix.android_getaddrinfo(Native Method)at libcore.io.ForwardingOs.android_getaddrinfo(ForwardingOs.java:55)at java.net.InetAddress.lookupHostByName(InetAddress.java:451)... 30 more
2、DNS 服務器意外掛掉驗證
對于DNS 服務器意外掛掉的情況,我們采用如下的方法模擬測試:
- 連接 WiFi,設置手機的 IP 地址為靜態 IP 地址,不修改之前 DHCP 分配的 IP 地址,但設置 DNS 服務器地址為無效的地址;
- 執行一個 HTTP 請求。
以 OkHttp 為例,在請求開始之后,將報出如下的異常:
java.net.UnknownHostException: Unable to resolve host "www.wolfcstech.com": No address associated with hostnameat java.net.InetAddress.lookupHostByName(InetAddress.java:470)at java.net.InetAddress.getAllByNameImpl(InetAddress.java:252)at java.net.InetAddress.getAllByName(InetAddress.java:215)at okhttp3.Dns$1.lookup(Dns.java:39)at com.netease.netlib.OkHttp3Utils$MyDns.lookup(OkHttp3Utils.java:45)at okhttp3.internal.connection.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:170)at okhttp3.internal.connection.RouteSelector.nextProxy(RouteSelector.java:136)at okhttp3.internal.connection.RouteSelector.next(RouteSelector.java:81)at okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:171)at okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:121)at okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:100)at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:120)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at com.netease.netlib.OkHttp3Utils$MyInterceptor.intercept(OkHttp3Utils.java:29)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:179)at okhttp3.RealCall$AsyncCall.execute(RealCall.java:129)at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)at java.lang.Thread.run(Thread.java:833)
Caused by: android.system.GaiException: android_getaddrinfo failed: EAI_NODATA (No address associated with hostname)at libcore.io.Posix.android_getaddrinfo(Native Method)at libcore.io.ForwardingOs.android_getaddrinfo(ForwardingOs.java:55)at java.net.InetAddress.lookupHostByName(InetAddress.java:451)... 30 more
這個異常與網絡斷開情況下報出的異常一模一樣,然而這一次從請求開始執行到異常報出,經歷的時間則要長得多,如我們上面看到的,這段時間長達 40 s。
3、DNS 服務器故障驗證
DNS 服務器故障主要是指 DNS 服務器確實查不到所請求的域名,可能是 DNS 服務器出了問題,也可能是為域名做的 DNS 配置還沒有生效等。
對于這種情況,我們采用如下方法模擬測試:
- 手機正常連接網絡;
- 以一個不存在的域名執行一個 HTTP 請求。
請求執行之后,將報出如下異常:
java.net.UnknownHostException: Unable to resolve host "www.wolfcsteach.com": No address associated with hostnameat java.net.InetAddress.lookupHostByName(InetAddress.java:470)at java.net.InetAddress.getAllByNameImpl(InetAddress.java:252)at java.net.InetAddress.getAllByName(InetAddress.java:215)at okhttp3.Dns$1.lookup(Dns.java:39)at com.netease.netlib.OkHttp3Utils$MyDns.lookup(OkHttp3Utils.java:45)at okhttp3.internal.connection.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:170)at okhttp3.internal.connection.RouteSelector.nextProxy(RouteSelector.java:136)at okhttp3.internal.connection.RouteSelector.next(RouteSelector.java:81)at okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:171)at okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:121)at okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:100)at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:120)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at com.netease.netlib.OkHttp3Utils$MyInterceptor.intercept(OkHttp3Utils.java:29)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:179)at okhttp3.RealCall$AsyncCall.execute(RealCall.java:129)at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)at java.lang.Thread.run(Thread.java:833)
Caused by: android.system.GaiException: android_getaddrinfo failed: EAI_NODATA (No address associated with hostname)at libcore.io.Posix.android_getaddrinfo(Native Method)at libcore.io.ForwardingOs.android_getaddrinfo(ForwardingOs.java:55)at java.net.InetAddress.lookupHostByName(InetAddress.java:451)... 30 more
4、所需診斷信息
依據上面的測試分析,診斷域名解析問題,需要如下診斷信息:
- 請求開始時間;
- 異常報出時間;
- 網絡是否連接;
- DNS 服務器 IP 地址列表;
- DNS 服務器是否可用(ping DNS 服務器 IP地址的結果);
- DNS 的結果(nslookup 結果)。
二、ConnectTimeoutException
等價異常類型:
- org.apache.http.conn.ConnectTimeoutException
- com.netease.mam.org.apache.http.conn.ConnectTimeoutException
錯誤說明: 連接超時
錯誤消息: Connect to /183.214.133.45:443 timed out
異常分析:
異常在 external/apache-http/src/org/apache/http/conn/scheme/PlainSocketFactory.java
的 connectSocket()
中,apache httpclient 拋出,由 Socket.connect()
中拋出的 SocketTimeoutException
引起。
可能原因:
- 設備接入的網絡本身帶寬比較低;
- 設備接入的網絡本身延遲比較高;
- 設備與服務器的網絡路徑中存在比較擁堵、負載比較重的節點;
- 網絡中路由節點的臨時性異常。
所需診斷信息:
- Traceroute 獲取的網絡路徑信息。
- 網絡路徑上不同節點的繁忙程度,可通過丟包率來近似地反映。
- 網絡連接條件(移動網絡/WiFI),網絡延時及帶寬。
三、SocketTimeoutException
異常: java.net.SocketTimeoutException
錯誤說明: socket 超時
異常分析:
在 OkHttp 處理 HTTP/2 的邏輯 (okhttp3.internal.http2.Http2Stream
) 中,會由于讀超時、寫超時而拋出該異常。
在 Android 中,java.net.PlainSocketImpl
的 accept(SocketImpl newImpl)
執行失敗,如果 errno 為 EAGAIN 將拋出該異常,如果使用 nio 的 ServerSocketChannelImpl
,異常將不會被實際拋出;此外,在阻塞 Socket 上讀取長度為 0 的數據時拋出此異常。
libcore.io.IoBridge
中,TCP 連接建立超時,將拋出該異常。libcore.io.IoBridge
中,接收數據失敗,會由于 errno
為 EAGAIN
拋出該異常。
在 external/conscrypt/src/main/native/org_conscrypt_NativeCrypto.cpp
中執行 SSL/TLS 握手動作、數據讀操作或數據寫操作超時,會拋出該異常。
1、子錯誤 - 讀超時
錯誤消息: Read timed out
異常分析:
在 external/conscrypt/src/main/native/org_conscrypt_NativeCrypto.cpp
中執行 SSL/TLS
數據讀操作超時,拋出該異常。
可能原因:
- 設備接入的網絡本身帶寬比較低;
- 設備接入的網絡本身延遲比較高;
- 設備與服務器的網絡路徑中存在比較擁堵、負載比較重的節點;
- 網絡中路由節點的臨時性異常。
所需診斷信息:
- Traceroute 獲取的網絡路徑信息。
- 網絡路徑上不同節點的繁忙程度,可通過丟包率來近似地反映。
- 網絡連接條件(移動網絡/WiFI),網絡延時及帶寬。
2、子錯誤 - SSL 握手超時
錯誤消息: SSL handshake timed out
異常分析:
在 external/conscrypt/src/main/native/org_conscrypt_NativeCrypto.cpp
中執行 SSL/TLS
握手動作超時,拋出該異常。
可能原因:
- 設備接入的網絡本身帶寬比較低;
- 設備接入的網絡本身延遲比較高;
- 設備與服務器的網絡路徑中存在比較擁堵、負載比較重的節點;
- 網絡中路由節點的臨時性異常。
所需診斷信息:
- Traceroute 獲取的網絡路徑信息。
- 網絡路徑上不同節點的繁忙程度,可通過丟包率來近似地反映。
- 網絡連接條件(移動網絡/WiFI),網絡延時及帶寬。
3、子錯誤 - 未知原因
錯誤消息: null
異常分析:
根據 SocketTimeoutException
拋出的所有情況綜合來看,這種異常主要由 HTTP 請求讀操作執行超時引起。
可能原因:
- 設備接入的網絡本身帶寬比較低;
- 設備接入的網絡本身延遲比較高;
- 設備與服務器的網絡路徑中存在比較擁堵、負載比較重的節點;
- 網絡中路由節點的臨時性異常。
所需診斷信息:
- 更加完整的異常堆棧
- Traceroute 獲取的網絡路徑信息。
- 網絡路徑上不同節點的繁忙程度,可通過丟包率來近似地反映。
- 網絡連接條件(移動網絡/WiFI),網絡延時及帶寬。
四、HttpHostConnectException
等價異常類型:
- org.apache.http.conn.HttpHostConnectException
- com.netease.mam.org.apache.http.conn.HttpHostConnectException
錯誤說明: 客戶端的數據包可以到達目標主機,但由于各種原因,連接建立失敗的問題。
錯誤消息: Connection to http://m7.music.126.net refused
可能原因:
- 連接的目標主機沒有開對應的端口,可能服務器發生故障
- 客戶端設置了代理,而代理進程并沒有跑起來。
針對這些原因,我們也可以做一些模擬和測試。
1、服務器故障驗證
對于這種情況,我們采用如下的方法來模擬測試,
- 指定我們執行 HTTP 請求時訪問的端口為一個無效的端口,如使用 URL ;
- 以 HttpClient 作為我們的 HttpStack 來執行 HTTP 請求。
請求執行之后,將報出如下異常:
org.apache.http.conn.HttpHostConnectException: Connection to https://www.wolfcstech.com:8080 refusedat org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:193)at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:169)at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:124)at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:366)at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:596)at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:517)at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:495)at com.netease.volleydemo.HttpClientUtils.httpGet(HttpClientUtils.java:21)at com.netease.volleydemo.MainActivity$HttpClientTask.doInBackground(MainActivity.java:152)at com.netease.volleydemo.MainActivity$HttpClientTask.doInBackground(MainActivity.java:142)at android.os.AsyncTask$2.call(AsyncTask.java:307)at java.util.concurrent.FutureTask.run(FutureTask.java:237)at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:246)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)at java.lang.Thread.run(Thread.java:833)
Caused by: java.net.ConnectException: failed to connect to /139.196.224.72 (port 8080): connect failed: ECONNREFUSED (Connection refused)at libcore.io.IoBridge.connect(IoBridge.java:124)at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:183)at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:452)at java.net.Socket.connect(Socket.java:938)at org.apache.http.conn.scheme.PlainSocketFactory.connectSocket(PlainSocketFactory.java:124)at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:149)... 15 more
Caused by: android.system.ErrnoException: connect failed: ECONNREFUSED (Connection refused)
04-27 14:45:44.822 at libcore.io.Posix.connect(Native Method)at libcore.io.BlockGuardOs.connect(BlockGuardOs.java:111)at libcore.io.IoBridge.connectErrno(IoBridge.java:137)at libcore.io.IoBridge.connect(IoBridge.java:122)... 20 more
org.apache.http.conn.HttpHostConnectException 的 errorMessage
向我們指出是服務器拒絕連接。
對于同樣的問題,如果以 OkHttp 作為 HttpStack 來執行請求的話,則將報出稍有不同的異常:
java.net.ConnectException: Failed to connect to www.wolfcstech.com/139.196.224.72:8080at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.java:222)at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:146)at okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:186)at okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:121)at okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:100)at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:120)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at com.netease.netlib.OkHttp3Utils$MyInterceptor.intercept(OkHttp3Utils.java:29)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:179)at okhttp3.RealCall$AsyncCall.execute(RealCall.java:129)at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)at java.lang.Thread.run(Thread.java:833)
Caused by: java.net.ConnectException: failed to connect to www.wolfcstech.com/139.196.224.72 (port 8080) after 10000ms: isConnected failed: ECONNREFUSED (Connection refused)at libcore.io.IoBridge.isConnected(IoBridge.java:234)at libcore.io.IoBridge.connectErrno(IoBridge.java:171)at libcore.io.IoBridge.connect(IoBridge.java:122)at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:183)at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:452)at java.net.Socket.connect(Socket.java:938)at okhttp3.internal.platform.AndroidPlatform.connectSocket(AndroidPlatform.java:63)at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.java:220)... 24 more
Caused by: android.system.ErrnoException: isConnected failed: ECONNREFUSED (Connection refused)at libcore.io.IoBridge.isConnected(IoBridge.java:223)... 31 more
OkHttp 拋出 java.net.ConnectException
,僅僅簡單地指出連接服務器失敗。
2、代理服務器故障驗證
這主要是指代理服務器進程沒有啟動,或代理設置存在問題。
對于這種情況,我們通過如下的方法來模擬測試:
- 使手機連接 WiFi;
- 為手機所連接的 WiFi 設備設置代理,其中代理服務器的地址為無效的 IP 地址,或端口為無效端口;
- 以 HttpClient 作為我們的 HttpStack 來執行 HTTP 請求。
請求執行之后,將報出如下異常:
org.apache.http.conn.HttpHostConnectException: Connection to http://10.240.252.44:8888 refusedat org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:193)at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:169)at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:124)at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:366)at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:596)at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:517)at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:495)at com.netease.volleydemo.HttpClientUtils.httpGet(HttpClientUtils.java:21)at com.netease.volleydemo.MainActivity$HttpClientTask.doInBackground(MainActivity.java:152)at com.netease.volleydemo.MainActivity$HttpClientTask.doInBackground(MainActivity.java:142)at android.os.AsyncTask$2.call(AsyncTask.java:307)at java.util.concurrent.FutureTask.run(FutureTask.java:237)at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:246)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)at java.lang.Thread.run(Thread.java:833)Caused by: java.net.ConnectException: failed to connect to /10.240.252.44 (port 8888): connect failed: ECONNREFUSED (Connection refused)at libcore.io.IoBridge.connect(IoBridge.java:124)at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:183)at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:452)at java.net.Socket.connect(Socket.java:938)at org.apache.http.conn.scheme.PlainSocketFactory.connectSocket(PlainSocketFactory.java:124)at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:149)... 15 moreCaused by: android.system.ErrnoException: connect failed: ECONNREFUSED (Connection refused)at libcore.io.Posix.connect(Native Method)at libcore.io.BlockGuardOs.connect(BlockGuardOs.java:111)at libcore.io.IoBridge.connectErrno(IoBridge.java:137)at libcore.io.IoBridge.connect(IoBridge.java:122)... 20 more
對于同樣的問題,OkHttp 報出了不同的異常:
java.net.ConnectException: Failed to connect to /10.240.252.44:8888at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.java:222)at okhttp3.internal.connection.RealConnection.connectTunnel(RealConnection.java:195)at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:144)at okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:186)at okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:121)at okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:100)at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:120)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at com.netease.netlib.OkHttp3Utils$MyInterceptor.intercept(OkHttp3Utils.java:29)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:179)at okhttp3.RealCall$AsyncCall.execute(RealCall.java:129)at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)at java.lang.Thread.run(Thread.java:833)
Caused by: java.net.ConnectException: failed to connect to /10.240.252.44 (port 8888) after 10000ms: isConnected failed: ECONNREFUSED (Connection refused)at libcore.io.IoBridge.isConnected(IoBridge.java:234)at libcore.io.IoBridge.connectErrno(IoBridge.java:171)
3、所需診斷信息
為了確認所報出的異常產生的根源,需要下的診斷信息:
- 連接的目標主機的 IP 地址;
- 連接建立的目標端口;
- 用戶設置的代理服務器地址和端口;
- TCP 連接目標 IP:端口 的結果。
4、參考資料
針對這個異常,網絡上有其它的一些資料和討論,相關鏈接如下:
- Stack Overflow 討論
- Android 5.0 拋出異常的相關源碼
- Android 官方文檔對該異常的說明
五、NoRouteToHostException
異常: java.net.NoRouteToHostException
錯誤說明: 無法連接遠程地址與端口。
錯誤消息: No route to host
可能原因:
- 防火墻的規則設置導致數據包無法被發送出去;
- 中間路由節點掛掉。
所需診斷信息:
- 用戶防火墻配置;
- 目標主機是否可以訪問(ping)。
參考資料:
- Stack Overflow 的討論
- Android 官方對異常的說明
六、SSLException
異常: javax.net.ssl.SSLException
錯誤說明: SSL 失敗
1、子錯誤 - SSL 讀期間連接重置
錯誤消息: Read error: ssl=0xf49f7200: I/O error during system call, Connection reset by peer
可能原因:
- 在網絡數據傳輸期間,TCP 連接被服務器異常結束。
- 遭受網絡 TCP Reset 攻擊。
異常分析:
在網絡數據傳輸期間,TCP 連接意外結束,由于服務器進程意外終止等原因,沒有經過正常的 TCP 連接斷開過程。后續客戶端向服務器發送 TCP 包,如數據包、ACK 包等,服務器返回 RST 包所致。
所需診斷信息:
- 與目標服務器目標端口的 TCP 連接測試結果,檢查目標服務器進程是否存活。
參考資料:
- 從TCP協議的原理來談談rst復位攻擊
2、子錯誤 - SSL 握手期間連接重置
錯誤消息: SSL handshake aborted: ssl=0x79c484c0: I/O error during system call, Connection reset by peer
可能原因:
- 在網絡數據傳輸期間,TCP 連接被服務器異常結束。
- 遭受網絡 TCP Reset 攻擊。
異常分析:
以 Android 6.0.0_r1 為例,僅有的出現 “SSL handshake aborted” 的位置為 http://androidxref.com/6.0.0_r1/xref/external/conscrypt/src/main/native/org_conscrypt_NativeCrypto.cpp — NativeCrypto_SSL_do_handshake()。
異常的 error message 構造過程為 NativeCrypto_SSL_do_handshake()
-> throwSSLExceptionWithSslErrors()
-> asprintf()
(error code 為 SSL_ERROR_SYSCALL )。
errorCode 由 SSL_do_handshake(SSL s)
返回,但在 SSL_do_handshake(SSL s)
,實際由 SSL *s
的回調函數 s
-> handshake_func
執行。handshake_func
在兩個地方賦值,對于服務器而言,在 SSL_set_accept_state()
中,該回調等于 ssl
-> method
-> ssl_accept
;對于客戶端而言,該回調在 SSL_set_connect_state()
中賦值,等于 ssl
-> method
-> ssl_connect
。 SSL *s
結構的 SSL_PROTOCOL_METHOD *method
,在 SSL 結構創建時賦值,SSL 結構的創建主要在 NativeCrypto_SSL_new()
-> SSL_new()
完成。SSL *s 結構的 SSL_PROTOCOL_METHOD *method
來源于 SSL_CTX 的 method。SSL_CTX
結構在 NativeCrypto_SSL_CTX_new()
中創建,SSL_CTX 結構的 method 來自于 SSLv23_method()
,實際為 TLS_method(void)
函數中的靜態結構,protocol_method
為 TLS_protocol_method
。
最終可以發現 SSL *s 的回調函數 s->handshake_func
為 ssl3_connect(SSL *s)
。在 ssl3_connect(SSL *s)
中,以一種狀態機模式的方式,逐步完成 SSL/TLS 握手,在遇到錯誤時退出。
因而錯誤的發生,是由于在 ssl3_connect(SSL *s)
執行期間,TCP 連接意外結束,可能由于服務器進程意外終止等原因,沒有經過正常的 TCP 連接斷開過程。后續客戶端向服務器發送 TCP 包,如數據包、ACK 包等,服務器返回 RST 包所致。但具體在 SSL/TLS 握手的哪個階段異常發生,則難以判斷。
所需診斷信息:
- 與目標服務器目標端口的 TCP 連接測試結果,檢查目標服務器進程是否存活。
七、SSLHandshakeException
異常: javax.net.ssl.SSLHandshakeException
錯誤說明: SSL/TLS 握手失敗
1、子錯誤 - 握手失敗
錯誤消息: Handshake failed
可能原因:
- 握手失敗,證書驗證失敗
- 未知證書頒發者;
- 不完整的證書鏈;
- 自簽名證書;
- 服務器主機名不匹配;
- 嚴格安全重新協商失敗;
- 遭遇了中間人攻擊。
- 握手失敗,協議協商失敗/握手格式不兼容
- 協議異常,服務器名稱標識不匹配
- 某些版本 SSL 庫的 bug
所需診斷信息:
- 異常的詳細錯誤消息;
- 客戶端支持的 SSL/TLS 版本;
- 客戶端設置的 SNI 信息;
- 服務器下發的證書鏈;
- 客戶端可用的加密套件。
對于特定的操作系統版本,其 SSL 庫的版本和根證書庫是固定的,對于這些重要的信息不再需要移動端上傳。此外,證書鏈消耗流量可能會比較大,大概在幾 KBytes
。
2、子錯誤 - 連接超時
錯誤消息: SSL handshake aborted: ssl=0xeec6d600: I/O error during system call, Connection timed out
可能原因:
- 設備接入的網絡本身帶寬比較低;
- 設備接入的網絡本身延遲比較高;
- 設備與服務器的網絡路徑中存在比較擁堵、負載比較重的節點;
- 網絡中路由節點的臨時性異常。
所需診斷信息:
- Traceroute 獲取的網絡路徑信息。
- 網絡路徑上不同節點的繁忙程度,可通過丟包率來近似地反映。
- 網絡連接條件(移動網絡/WiFI),網絡延時及帶寬。
3、子錯誤 - 連接重置
錯誤消息: SSL handshake aborted: ssl=0xea0e4f00: I/O error during system call, Connection reset by peer
可能原因:
- 在網絡數據傳輸期間,TCP 連接被服務器異常結束。
- 遭受網絡 TCP Reset 攻擊。
錯誤原因分析:
在網絡數據傳輸期間,TCP 連接意外結束,可能由于服務器進程意外終止等原因,沒有經過正常的 TCP 連接斷開過程。后續客戶端向服務器發送 TCP 包,如數據包、ACK 包等,服務器返回 RST 包所致。
所需診斷信息:
- 與目標服務器目標端口的 TCP 連接測試結果,檢查目標服務器進程是否存活。
4、子錯誤 - 連接被關閉
錯誤消息: Connection closed by peer
可能原因:
- 在網絡數據傳輸期間,TCP 連接被服務器異常結束。
錯誤原因分析:
在網絡數據傳輸期間,由于服務器回收 TCP 連接,或服務器進程意外結束,連接被提前結束,正常的 TCP 連接斷開過程完成所致。
所需診斷信息:
- 與目標服務器目標端口的 TCP 連接測試結果,檢查目標服務器進程是否存活。
八、SSLProtocolException
異常: javax.net.ssl.SSLProtocolException
錯誤說明: 協議異常
1、子錯誤 - 記錄MAC無效的讀失敗
錯誤消息: Read error: ssl=0xf475ee00: Failure in SSL library, usually a protocol error error:140943FC:SSL routines:SSL3_READ_BYTES:sslv3 alert bad record mac (external/openssl/ssl/s3_pkt.c:1308 0xe27a8ee0:0x00000003)
可能原因:
- 數據在傳輸過程中被篡改。
- MAC 方式不支持。
- 某些版本SSL 庫的 bug。
所需診斷信息:
- 客戶端支持的 SSL/TLS 版本;
- 客戶端可用的加密套件。
2、子錯誤 - 協議版本導致的讀失敗
錯誤消息: Read error: ssl=0xdbab7300: Failure in SSL library, usually a protocol error error:100c542e:SSL routines:ssl3_read_bytes:TLSV1_ALERT_PROTOCOL_VERSION (external/boringssl/src/ssl/s3_pkt.c:972 0xd3fb2d20:0x00000001)
可能原因:
- 遭遇降級攻擊;
- 某些版本 SSL 庫的 bug。
所需診斷信息:
- 客戶端支持的 SSL/TLS 版本;
- 服務器下發的證書鏈;
- 客戶端可用的加密套件。
對于特定操作系統版本,其 SSL 庫版本和根證書庫是固定的,對于這些重要信息不再需要移動端上傳。此外,證書鏈消耗流量可能會比較大,大概在幾 KBytes
。
九、NoHttpResponseException
等價異常類型:
- org.apache.http.NoHttpResponseException
- com.netease.mam.org.apache.http.NoHttpResponseException
錯誤說明: 服務器響應異常
錯誤消息: The target server failed to respond
可能原因:
- 服務器故障。
十、InterruptedIOException
異常: java.io.InterruptedIOException
錯誤說明: IO 中斷
1、子錯誤 - 連接已關閉
錯誤消息: Connection has been shut down.
異常分析:
異常拋出的位置為 external/apache-http/src/org/apache/http/impl/conn/AbstractClientConnAdapter.java
的 assertNotAborted()
。
可能原因:
客戶端在發送請求或接收響應時,發現連接已經被終止,請求被終止/取消。
IOException
異常: java.io.IOException
錯誤說明: IO 失敗
十一、IOException
異常: java.io.IOException
錯誤說明: IO 失敗
1、子錯誤 - 連接關閉
錯誤消息: Connection already shutdown
異常分析:
異常拋出的位置為 external/apache-http/src/org/apache/http/impl/conn/DefaultClientConnection.java
的 opening()
方法。
可能原因:
- 連接打開動作執行過程中,被客戶端關閉,請求被終止/取消。
2、子錯誤 - 請求被終止
錯誤消息: Request already aborted
異常分析:
異常拋出的位置為 external/apache-http/src/org/apache/http/client/methods/HttpRequestBase.java
的 setConnectionRequest()
和 setReleaseTrigger()
中,設置連接請求和 ConnectionReleaseTrigger
時執行檢查,發現請求被客戶端終止。
可能原因:
- 請求被終止/取消。
十二、SocketException
異常: java.net.SocketException
錯誤說明: Socket 異常
1、子錯誤 - 讀消息連接重置
錯誤消息: recvfrom failed: ECONNRESET (Connection reset by peer)
可能原因:
- 在網絡數據傳輸期間,TCP 連接被服務器異常結束。
- 遭受網絡 TCP Reset 攻擊。
錯誤原因分析:
在網絡數據傳輸期間,TCP 連接意外結束,由于服務器進程意外終止等原因,沒有經過正常的 TCP 連接斷開過程。后續客戶端向服務器發送 TCP 包,如數據包、ACK 包等,服務器返回 RST 包所致。
所需診斷信息:
- 與目標服務器目標端口的 TCP 連接測試結果,檢查目標服務器進程是否存活。
2、子錯誤 - 連接重置
錯誤消息: Connection reset
可能原因:
- 在網絡數據傳輸期間,TCP 連接被服務器異常結束。
- 遭受網絡 TCP Reset 攻擊。
錯誤原因分析:
在網絡數據傳輸期間,TCP 連接意外結束,可能由于服務器進程意外終止等原因,沒有經過正常的 TCP 連接斷開過程。后續客戶端向服務器發送 TCP 包,如數據包、ACK 包等,服務器返回 RST 包所致。
所需診斷信息:
- 與目標服務器目標端口的 TCP 連接測試結果,檢查目標服務器進程是否存活。
3、子錯誤 - Socket 關閉
錯誤消息: Socket closed
異常分析:
異常拋出的位置為 libcore/luni/src/main/java/libcore/io/IoBridge.java 的 isConnected()。
可能原因:
- 連接被客戶端關閉,請求被終止/取消。
十三、ConnectException
異常: java.net.ConnectException
錯誤說明: 連接失敗
異常分析:
ConnectException
異常在 libcore.io.IoBridge
的 connect(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs)
和 isConnected()
中拋出。在執行 socket
連接建立操作時,底層操作失敗。錯誤信息中都會包含連接的目標主機的 IP,嘗試連接的時間, errno
值及 errno
值對應的錯誤消息。通常可根據 errno
判斷失敗原因。
1、子錯誤 - 連接被拒絕
錯誤消息: failed to connect to /119.84.111.126 (port 80) after 30000ms: isConnected failed: ECONNREFUSED (Connection refused)
參考前面 HttpHostConnectException
相關說明。
2、子錯誤 - 主機不可達
錯誤消息: failed to connect to /153.101.65.23 (port 80) after 30000ms: isConnected failed: EHOSTUNREACH (No route to host)
可能原因:
- 防火墻的規則設置導致數據包無法被發送出去;
- 中間路由節點掛掉。
異常分析:
引發了ICMP目的不可達錯誤,這認為是軟錯誤。client核心保存消息并且荏苒發送SYN。。如果超出一個確定的時間。保存的ICMP錯誤返回給進程 EHOSTUNREACH或者ENETUNREACH。ENETUNREACH被認為是過時的。所以返回的應該是EHOSTUNREACH。
所需診斷信息:
- 用戶防火墻配置;
- 目標主機是否可以訪問(ping)。
3、子錯誤 - 網絡不可達
錯誤消息: failed to connect to /59.111.160.195 (port 80) after 30000ms: connect failed: ENETUNREACH (Network is unreachable)
可能原因:
- 防火墻的規則設置導致數據包無法被發送出去;
- 中間路由節點掛掉。
所需診斷信息:
- 用戶防火墻配置;
- 目標主機是否可以訪問(ping)。
十四、ClientProtocolException
等價異常類型:
- com.netease.mam.org.apache.http.client.ClientProtocolException
錯誤說明: 協議錯誤
異常分析:
異常拋出的位置為 external/apache-http/src/org/apache/http/impl/client/AbstractHttpClient.java
的 execute()
中。在執行 RequestDirector.execute()
時,捕獲到 HttpException
異常時拋出該異常。 HttpException
異常拋出的位置主要有以下幾個:
external/apache-http/src/org/apache/http/impl/client/DefaultRequestDirector.java
的createTunnelToTarget()
中通過 HTTP 的 CONNECT 方法建立隧道鏈接時,對端返回了小于 200 的響應碼,隧道連接建立失敗。external/apache-http/src/org/apache/http/impl/conn/ProxySelectorRoutePlanner.java
的determineProxy()
中 URI 格式錯誤。URI 通過目標主機的 scheme ,主機名,端口等構造。external/apache-http/src/org/apache/http/impl/conn/ProxySelectorRoutePlanner.java
的determineProxy()
中代理地址格式錯誤。
1、子錯誤 - 未知原因
錯誤消息: null
可能原因:
- 這個錯誤主要與客戶端的 HTTP 代理服務器設置有關。
所需診斷信息:
- 如果是 HTTPS 請求,通過 HTTP 的 CONNECT 方法建立隧道鏈接時,CONNECT 請求的完整響應內容。
- 代理服務器配置。