javax.net.ssl.SSLException:读取错误:ssl = 0x9524b800:系统调用期间的I / O错误,同级连接重置

时间:2015-05-29 20:37:50

标签: android http ssl retrofit okhttp

我们的客户在过去几周内开始看到这些“SSLException错误 - 由同行重置连接”中的100个,我无法弄清楚为什么

  1. 我们正在使用带有okhttp的Retrofit,没有特殊的配置

    public class OkHttpClientProvider implements IOkHttpClientProvider {
    
        OkHttpClient okHttpClient;
    
        public OkHttpClientProvider() {
            this.okHttpClient = createClient();
        }
    
        public OkHttpClient getOkHttpClient() {
            return this.okHttpClient;
        }
    
        private OkHttpClient createClient() {
            return new OkHttpClient();
        }
    }
    
  2. 上述客户提供商是单身人士。 RestAdapter是使用这个注入的客户端构建的(我们使用匕首) -

    RestAdapter.Builder restAdapterBuilder = new RestAdapter.Builder()
                                            .setConverter(converter)
                                            .setEndpoint(networkRequestDetails.getServerUrl())
                                            .setClient(new OkClient(okHttpClientProvider.getOkHttpClient()))
                                            .setErrorHandler(new NetworkSynchronousErrorHandler(eventBus))
                                            );
    

    基于堆栈溢出解决方案我发现了什么 -

    1. 服务器上的保持活动持续时间为180秒,OkHttp的默认值为300秒

    2. 服务器在其标题中返回“Connection:close”,但客户端请求发送“Connection:keepAlive”

    3. 服务器支持TLS 1.0 / 1.1 / 1.2并使用Open SSL

    4. 我们的服务器最近搬到了另一个托管服务提供商的另一个地理区域,因此我不知道这些是否是DNS故障

    5. 我们尝试调整keepAlive之类的东西,在服务器上重新配置OpenSSL,但由于某种原因,Android客户端不断收到此错误

    6. 当您尝试使用该应用程序发布内容或拉动刷新时它会立即发生(它甚至没有进入网络或在此异常发生之前有延迟,这意味着连接已经存在)破碎)。但尝试多次以某种方式“修复它”,我们取得了成功。它会在以后再次发生

    7. 我们已经使服务器上的DNS条目无效,看看是什么导致它但是没有帮助

    8. 主要发生在LTE上,但我也在Wifi上看过它

    9. 我不想禁用keep alive,因为大多数现代客户端都没有这样做。此外,我们正在使用OkHttp 2.4,这是后冰淇淋三明治设备的一个问题,所以我希望它应该照顾这些潜在的网络问题。 iOS客户端也获得了这些例外但接近100倍(iOS客户端使用AFNetworking 2.0)。我正在努力寻找新的东西来尝试这一点,任何帮助/想法?

      更新 - 通过okhttp

      添加完整堆栈跟踪
            retrofit.RetrofitError: Read error: ssl=0x9dd07200: I/O error during system call, Connection reset by peer
                    at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:390)
                    at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:240)
                    at java.lang.reflect.Proxy.invoke(Proxy.java:397)
                    at $Proxy15.getAccessTokenUsingResourceOwnerPasswordCredentials(Unknown Source)
                    at com.company.droid.repository.network.NetworkRepository.getAccessTokenUsingResourceOwnerPasswordCredentials(NetworkRepository.java:76)
                    at com.company.droid.ui.login.LoginTask.doInBackground(LoginTask.java:88)
                    at com.company.droid.ui.login.LoginTask.doInBackground(LoginTask.java:23)
                    at android.os.AsyncTask$2.call(AsyncTask.java:292)
                    at java.util.concurrent.FutureTask.run(FutureTask.java:237)
                    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
                    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
                    at java.lang.Thread.run(Thread.java:818)
             Caused by: javax.net.ssl.SSLException: Read error: ssl=0x9dd07200: I/O error during system call, Connection reset by peer
                    at com.android.org.conscrypt.NativeCrypto.SSL_read(Native Method)
                    at com.android.org.conscrypt.OpenSSLSocketImpl$SSLInputStream.read(OpenSSLSocketImpl.java:699)
                    at okio.Okio$2.read(Okio.java:137)
                    at okio.AsyncTimeout$2.read(AsyncTimeout.java:211)
                    at okio.RealBufferedSource.indexOf(RealBufferedSource.java:306)
                    at okio.RealBufferedSource.indexOf(RealBufferedSource.java:300)
                    at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:196)
                    at com.squareup.okhttp.internal.http.HttpConnection.readResponse(HttpConnection.java:191)
                    at com.squareup.okhttp.internal.http.HttpTransport.readResponseHeaders(HttpTransport.java:80)
                    at com.squareup.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:917)
                    at com.squareup.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:793)
                    at com.squareup.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:439)
                    at com.squareup.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:384)
                    at com.squareup.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:497)
                    at com.squareup.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:105)
                    at com.squareup.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:25)
                    at retrofit.client.UrlConnectionClient.readResponse(UrlConnectionClient.java:73)
                    at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:38)
                    at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:321)
                    at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:240)
                    at java.lang.reflect.Proxy.invoke(Proxy.java:397)
                    at $Proxy15.getAccessTokenUsingResourceOwnerPasswordCredentials(Unknown Source)
                    at com.company.droid.repository.network.NetworkRepository.getAccessTokenUsingResourceOwnerPasswordCredentials(NetworkRepository.java:76)
                    at com.company.droid.ui.login.LoginTask.doInBackground(LoginTask.java:88)
                    at com.company.droid.ui.login.LoginTask.doInBackground(LoginTask.java:23)
                    at android.os.AsyncTask$2.call(AsyncTask.java:292)
                    at java.util.concurrent.FutureTask.run(FutureTask.java:237)
                    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
                    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
                    at java.lang.Thread.run(Thread.java:818)
            ]}
      

8 个答案:

答案 0 :(得分:82)

最近我在处理一些遗留代码时遇到了这个问题。谷歌搜索后我发现问题无处不在,但没有任何具体的解决方案。我处理了异常消息的各个部分并在下面进行了分析。

<强>分析:

  1. SSLException:SSL(安全套接字层)发生异常,它在JDK的javax.net.ssl包中实现(openJDK/oracleJDK/AndroidSDK
  2. Read error ssl=# I/O error during system call:从安全套接字读取时出错。它发生在使用本机系统库/驱动程序时。请注意,所有平台solaris,Windows等都有自己的套接字库,SSL使用它们。 Windows使用WINSOCK库。
  3. Connection reset by peer:系统库(Solaris报告ECONNRESET,Windows报告WSAECONNRESET)报告此消息,数据传输中使用的套接字不再可用,因为现有连接被远程主机强行关闭。需要在主机和客户端之间创建新的安全路径
  4. <强>原因:

    了解这个问题,我尝试找到连接重置背后的原因,我想出了以下原因:

    • 远程主机上的对等应用程序突然停止,主机重新启动,主机或远程网络接口被禁用,或远程主机使用硬关闭。
    • 如果由于保持活动活动在一个或多个操作正在进行时检测到故障而导致连接中断,则也可能导致此错误。正在进行的操作因Network dropped connection on reset(On Windows(WSAENETRESET))而失败,后续操作因Connection reset by peer(On Windows(WSAECONNRESET))而失败。
    • 如果目标服务器受防火墙保护,在大多数情况下都是如此,则与端口关联的生存时间(TTL)或超时会在给定超时时强制关闭空闲连接。 这是我们感兴趣的事情

    解决:

    1. 无法以任何方式处理服务器端的事件,例如突然停止服务,重新启动,禁用网络接口。
    2. 在服务器端,使用更高的生存时间(TTL)或超时值(例如3600秒)为给定端口配置防火墙。
    3. 客户可以“尝试”保持网络有效以避免或减少Connection reset by peer
    4. 正常情况下,网络流量会使连接保持活动状态,并且不会经常出现问题/异常。 强Wifi最少Connection reset by peer
    5. 对于移动网络 2G,3G和4G ,其中分组数据传送是间歇性的并且取决于移动网络可用性,它可能不会重置服务器端的TTL定时器并导致{ {1}}。
    6. 以下是建议在各种论坛上设置以解决问题的条款

      • Connection reset by peer仅在结束连接时使用。如果主机需要时间来连接更高的值,则客户端会等待连接。
      • ConnectionTimeout::套接字超时 - 它表示接收数据包以将连接视为活动的最长时间。如果在给定时间内未收到任何数据,则认为连接已停止/已断开。
      • SoTimeout:当数据排队等待发送并且在套接字上调用close套接字函数时,不应该关闭套接字的时间。
      • Linger:您是否要禁用保存和累积TCP数据包的缓冲区,并在达到阈值后发送它们?将此设置为true将跳过TCP缓冲,以便立即发送每个请求。网络速度下降可能是由于较小和更频繁的数据包传输导致网络流量增加所致。

      因此,上述参数都不能帮助保持网络活跃,从而无效。

      我找到了一个可能有助于解决此功能问题的设置

      TcpNoDelay

      我是如何解决我的问题的?

      • 设置setKeepAlive(true) setSoKeepalive(HttpParams params, enableKeepalive="true")
      • 抓住HttpConnectionParams.setSoKeepAlive(params, true)并检查SSLException
      • 的异常消息
      • 如果发现异常,请存储下载/读取进度并创建新连接。
      • 如果可能,请继续下载/阅读,然后重启下载

      我希望细节有所帮助。快乐的编码...

答案 1 :(得分:11)

如果使用ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 并遇到类似问题,那么这可能有所帮助:

在此sslTesturl上扫描您的域名,看看您的设备版本是否允许连接。

如果由于TLS支持而导致较低版本的设备(如&lt; Android 4.4.2等)无法连接,请尝试将其添加到您的Nginx配置文件中,

body {
  background: #ccc;
}

.white,
.black {
  height: 200px;
}

.white {
  background: white;
}

.black {
  background: black;
}

答案 2 :(得分:5)

今天早上我们有同样的问题并且解决了......希望这有帮助...

IIS 8上的SSL

  1. 昨天一切正常,昨晚我们的SSL在IIS网站上更新了。
  2. 在检查网站绑定到SSL时,注意到IIS8有一个新的复选框 Require Server Name Indication ,但未检查,因此先启用它。
  3. 引发了这个问题。
  4. 回到IIS,禁用了复选框....问题已解决!!!!
  5. 希望这有帮助!!!

答案 3 :(得分:1)

除了Android N(API级别24)和低于Android 5.1(API级别22)之外,Android默认支持SSL实现。
在服务器端实施SSL后,在API级别22设备以下进行API调用时出现错误;是在创建OkHttpClient客户端对象时,并通过添加connectionSpecs()方法OkHttpClient.Builder类进行修复的。

收到的错误是

  

响应失败:javax.net.ssl.SSLException:SSL握手已中止:   ssl = 0xb8882c00:系统调用时发生I / O错误,对等方重置了连接

所以我通过添加

等支票来解决此问题
if ( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
            // Do something for below api level 22
            List<ConnectionSpec> specsList = getSpecsBelowLollipopMR1(okb);
            if (specsList != null) {
                okb.connectionSpecs(specsList);
            }
        }

也适用于Android N(API级别24);在进行

之类的HTTP调用时出现错误
  

HTTP失败:javax.net.ssl.SSLHandshakeException:握手失败

,这可以通过添加针对Android 7的支票来解决,例如

if (android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.N){
            // Do something for naugat ; 7
            okb.connectionSpecs(Collections.singletonList(getSpec()));
        }

所以我最后的OkHttpClient对象将是:

         OkHttpClient client
         HttpLoggingInterceptor httpLoggingInterceptor2 = new
         HttpLoggingInterceptor();
         httpLoggingInterceptor2.setLevel(HttpLoggingInterceptor.Level.BODY);

         OkHttpClient.Builder okb = new OkHttpClient.Builder()
                 .addInterceptor(httpLoggingInterceptor2)
               .addInterceptor(new Interceptor() {
                     @Override
                     public Response intercept(Chain chain) throws IOException {
                         Request request = chain.request();
                         Request request2 = request.newBuilder().addHeader(AUTH_KEYWORD, AUTH_TYPE_JW + " " + password).build();
                         return chain.proceed(request2);
                     }
                 }).connectTimeout(30, TimeUnit.SECONDS)
                 .writeTimeout(30, TimeUnit.SECONDS)
                 .readTimeout(30, TimeUnit.SECONDS);

         if (android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.N){
             // Do something for naugat ; 7
             okb.connectionSpecs(Collections.singletonList(getSpec()));
         }

         if ( Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
             List<ConnectionSpec> specsList = getSpecsBelowLollipopMR1(okb);
             if (specsList != null) {
                 okb.connectionSpecs(specsList);
             }
         }

         //init client
         client = okb.build();

getSpecsBelowLollipopMR1函数就像

   private List<ConnectionSpec> getSpecsBelowLollipopMR1(OkHttpClient.Builder okb) {

        try {

            SSLContext sc = SSLContext.getInstance("TLSv1.2");
            sc.init(null, null, null);
            okb.sslSocketFactory(new Tls12SocketFactory(sc.getSocketFactory()));

            ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
                    .tlsVersions(TlsVersion.TLS_1_2)
                    .build();

            List<ConnectionSpec> specs = new ArrayList<>();
            specs.add(cs);
            specs.add(ConnectionSpec.COMPATIBLE_TLS);

            return specs;

        } catch (Exception exc) {
            Timber.e("OkHttpTLSCompat Error while setting TLS 1.2"+ exc);

            return null;
        }
    }

可以在下面的链接中找到Tls12SocketFactory类(由gotev评论):

https://github.com/square/okhttp/issues/2372


要获得更多支持,请在下面添加一些链接,以帮助您详细了解

  

https://developer.android.com/training/articles/security-ssl

     

D/OkHttp: <-- HTTP FAILED: javax.net.ssl.SSLException: SSL handshake aborted: ssl=0x64e3c938: I/O error during system call, Connection reset by peer

答案 4 :(得分:0)

此错误消息的另一个可能原因是服务器或负载均衡器阻止了HTTP方法。

阻止未使用的HTTP方法似乎是standard security practice。我们遇到了这个问题,因为HEAD被负载均衡器阻止了(但奇怪的是,不是所有的负载均衡服务器,导致它只在某些时候失败)。通过临时更改它以使用GET方法,我能够测试请求本身是否正常工作。

iOS上的错误代码是: 请求应用程序代码时出错:错误域= NSURLErrorDomain代码= -1005&#34;网络连接丢失。&#34;

答案 5 :(得分:0)

我在Android 5.1.1个设备上使用okhttp/4.0.0-RC1发送网络请求时遇到此错误。在服务器端设置标头Content-Length: <sizeof response>解决了该问题。

答案 6 :(得分:0)

我的问题是模拟器genymotion中的TIMEZONE。将TIMEZONE ANDROID EMULATOR更改为TIMEZONE SERVER,即可解决问题。

reference

答案 7 :(得分:0)

好吧,优雅地处理异常可能非常困难。

问题: 每当我向我的网络服务发出请求时,我都会收到此 javax.net.ssl.SSLException 错误,因为我完全知道我已经用完了我订阅的互联网数据包,但我的移动应用程序确认我已连接检查 Connectivity.NetworkAccess == NetworkAccess.Internet 后访问互联网。最糟糕的是,javax.net.ssl.SSLException 错误会在数据包耗尽的任何时候使应用程序崩溃。

我的案例: 就我而言,我正在测试如果用户在有互联网连接时尝试登录会出现什么问题,但他们正在使用的特定 ISP 没有可用的数据包订阅。好吧,应用程序因 javax.net.ssl.SSLException 错误而崩溃,这就是我得到的结果。

我的解决方案: 我在谷歌上搜索了处理这个问题的最佳方法,但我得到的答案针对不同的人,所以我决定弄清楚如何实现我的。

首先,作为一般的事情,我用 try-catch 块包围了我的网络请求代码,如下所示;

    public static async Task<LoginResponse> Login(string email, string password)
    {
        try
        {
            var login = new Login()
            {
                email_address = email,
                password = password
            };

            var httpClient = new HttpClient();
            var json = JsonConvert.SerializeObject(login);
            var content = new StringContent(json, Encoding.UTF8, "application/json");
            var response = await httpClient.PostAsync(AppBaseUrl.ApiBaseUrl + "Users/LoginUser", content).ConfigureAwait(false);
            var jsonResult = await response.Content.ReadAsStringAsync();

            var apiResponse = JsonConvert.DeserializeObject<LoginResponse>(jsonResult);
            
            return apiResponse;
        }
        catch (Exception ex)
        {
            return new LoginResponse
            {
                code = 0,
                responsebody = null,
                message = ex.Message
            };
        }
    }

上面几行代码的重点是在 Catch 块中,我从我的 API 返回登录响应。 注意: code = 0 表示网络请求不成功。

在我的 ViewModel 中,我添加了以下代码行;

var loginResponse = await Login(Email, Password);

if (loginResponse.code == 1)
{
     // Do something when Login is successful.
}
else
{
     // Do something when Login is not successful.
     await Application.Current.MainPage.DisplayAlert("Oops!", loginResponse.message + ". Try again", "Ok"); // This line will display a message like "Connection closed by peer. Try again"
}

我附上了在物理 Android 设备上运行的应用的屏幕截图。 Screenshot of the app

最后的想法: 我仍然相信在这种情况下我自己的错误处理方法甚至可以改进。这样做的好处是它可以跨平台处理。但是,该应用程序不再崩溃,我希望这可以帮助那里的人。