【问题标题】:Android Error: java.net.SocketException: Socket closedAndroid 错误:java.net.SocketException:套接字已关闭
【发布时间】:2014-08-28 13:07:48
【问题描述】:

我每周都会在崩溃日志中看到此错误数百次,但此时我已经花了数周时间试图追查该错误,但没有任何成功。我无法在我的任何设备上重现它。这是堆栈跟踪:

Posix.java:-2 in "libcore.io.Posix.recvfromBytes"
Posix.java:131 in "libcore.io.Posix.recvfrom"
BlockGuardOs.java:164 in "libcore.io.BlockGuardOs.recvfrom"
IoBridge.java:513 in "libcore.io.IoBridge.recvfrom"
PlainSocketImpl.java:489 in "java.net.PlainSocketImpl.read"
PlainSocketImpl.java:46 in "java.net.PlainSocketImpl.access$000"
PlainSocketImpl.java:241 in "java.net.PlainSocketImpl$PlainSocketInputStream.read"
AbstractSessionInputBuffer.java:103 in "org.apache.http.impl.io.AbstractSessionInputBuffer.fillBuffer"
AbstractSessionInputBuffer.java:191 in "org.apache.http.impl.io.AbstractSessionInputBuffer.readLine"
DefaultResponseParser.java:82 in "org.apache.http.impl.conn.DefaultResponseParser.parseHead"
AbstractMessageParser.java:174 in "org.apache.http.impl.io.AbstractMessageParser.parse"
AbstractHttpClientConnection.java:180 in "org.apache.http.impl.AbstractHttpClientConnection.receiveResponseHeader"
DefaultClientConnection.java:235 in "org.apache.http.impl.conn.DefaultClientConnection.receiveResponseHeader"
AbstractClientConnAdapter.java:259 in "org.apache.http.impl.conn.AbstractClientConnAdapter.receiveResponseHeader"
HttpRequestExecutor.java:279 in "org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse"
HttpRequestExecutor.java:121 in "org.apache.http.protocol.HttpRequestExecutor.execute"
DefaultRequestDirector.java:428 in "org.apache.http.impl.client.DefaultRequestDirector.execute"
AbstractHttpClient.java:555 in "org.apache.http.impl.client.AbstractHttpClient.execute"
AbstractHttpClient.java:487 in "org.apache.http.impl.client.AbstractHttpClient.execute"
AbstractHttpClient.java:465 in "org.apache.http.impl.client.AbstractHttpClient.execute"
Utilities.java:484 in "com.myapp.android.Utilities$8.run"

这是错误来自的代码块...崩溃发生的确切位置是HttpResponse response = httpclient.execute(httppost);

 public static HttpPost postData(String URL, final List<NameValuePair> params, final Handler handler) {
        // Create a new HttpClient and Post Header
        //android.util.Log.d("Utilities", "Called postData");
        final HttpClient httpclient = new DefaultHttpClient();
        //httpclient.
        final HttpPost httppost = new HttpPost(URL);
        final Message msg = new Message();
        final Bundle dataBundle = new Bundle();
        final ByteArrayOutputStream out = new ByteArrayOutputStream();

        new Thread(){
            @Override
            public void run(){
                String error = "";
                String data = "";
                try {
                    httppost.setEntity(new UrlEncodedFormEntity(params));
                    HttpResponse response = httpclient.execute(httppost);
                    StatusLine statusLine = response.getStatusLine();
                    if(statusLine.getStatusCode() == HttpStatus.SC_OK){
                        response.getEntity().writeTo(out);
                        out.close();
                        data = out.toString();
                    } else{
                        error = EntityUtils.toString(response.getEntity());
                    }
                } catch (ClientProtocolException e) {
                    AirbrakeNotifier.notify(e);
                    error = e.toString();
                } catch (IOException e) {
                    AirbrakeNotifier.notify(e);
                    error = e.toString();
                } catch (Exception ex) {
                    AirbrakeNotifier.notify(ex);
                    error = ex.toString();
                }
                dataBundle.putString("error", error);
                dataBundle.putString("data", data);
                msg.setData(dataBundle);
                handler.dispatchMessage(msg);
            }
        }.start();
        return httppost;
    }

非常感谢您对最终解决这个问题的任何帮助!

【问题讨论】:

  • 调试不可见的代码很难。
  • 对不起,添加了一些代码来看看。
  • 另一个线程正在关闭套接字,而这个线程在 read() 中被阻塞。您是否有机会重用 HTTPClient?
  • 不,没有其他线程关闭套接字。每次我们想在应用中发出请求时,我们都会使用postData 函数。
  • 我假设您的描述意味着您的应用程序的用户正在看到此崩溃并且您无法重现它。如果是这种情况,也许他们只是互联网连接不好,您需要更优雅地处理这些错误。在测试您的应用程序时,请尝试在您的应用程序运行时切断互联网连接,看看它是否会重现类似的东西。如果是时间问题,请在故障线之前休眠,这样您就可以确保在继续之前切断数据连接。

标签: java android android-asynctask android-studio androidhttpclient


【解决方案1】:

在我看来,这个问题的罪魁祸首不是你的应用程序,而是远程端(即 HTTP 服务器)。最可能的情况是 HTTP 服务器突然重置连接,这会导致您的应用程序中出现SocketException。在生产环境中,这些事情经常发生。这可能是由于 HTTP 服务器过载,某些可能导致服务器关闭的异常情况(HTTP 请求泛滥,甚至远程服务器耗尽资源时请求数量增加;服务器也可能耗尽它的本地套接字池...原因可能有几十个)。

如果这些错误的比例与成功的 HTTP 请求相比较低,我不会太担心,我只需将那段代码包装到 try { ... } catch (SocketException e) { ... } 语句中并向用户显示一个对话框告诉他们请求失败,他们应该重试。

我肯定会尝试确定这种行为的原因:我会尝试匹配其中一个异常的时间,并尝试挖掘几乎到那个时间的 HTTP 服务器日志以尝试确定原因这种突然断开连接(假设您可以访问该日志和其他诊断工具)。正如我之前所说,这可能是一件愚蠢的事情,或者调试起来有点复杂,但我敢打赌这就是问题所在。

【讨论】:

    【解决方案2】:

    “java.net.SocketException: Socket closed”异常可能在各种情况下发生。服务器端像 nKn 建议的那样关闭了连接,或者客户端(您的应用程序)关闭了连接。即使您不知道这样做,也可能有一些不太明显的代码可能导致关闭套接字,例如 Thread.interrupt() 或 ExecutorService.shutdownNow()。

    另一方面,如果它实际上发生在服务器端,我建议您实施重试 - 3 次尝试是常见的做法,通常就足够了。

    【讨论】:

    • 您将如何实施重试?
    • 您可以通过执行简单的 for 或 while 循环来实现重试。这可以通过所有 HTTP 客户端完成。但最简单的方法是使用一个 HTTPClient 类,它会自动为您完成。 hc.apache.org/httpclient-3.x/tutorial.html 上的 Apache HttpClient 文档指出:“默认情况下,HttpClient 将自动尝试从非致命错误中恢复,即当抛出一个普通的 IOException 时。只要请求,HttpClient 将重试该方法三次从未完全传输到目标服务器。”
    【解决方案3】:

    您当前正在接受客户端库配置的任何默认值。也许您想更多地控制您的 Httpclient 库,特别是关于套接字 TIMEOUT 设置。不要等待服务器做一些意想不到的事情。设置比默认值更短的超时时间,并以对您的用户有意义的方式控制错误“稍后尝试 msg”....

    如果您使用默认的 android httpclient,您可能需要查看与较新的 Apache 客户端版本保持同步的替代方案...

    https://hc.apache.org/httpcomponents-client-4.3.x/android-port.html

    https://code.google.com/p/httpclientandroidlib/

    general background async client

    请注意,使用其中任何一种,您都可以考虑(在 Wifi 上)或(在 4G 上)您可以拨打detailed timeout profiles,在其中使用如下代码控制超时:

    public void create(int method, final String url, final String data) {
        this.method = method;
        this.url = url;     
        this.data = data;
        if(method == GET){
            this.config = RequestConfig.custom()
                .setConnectTimeout(6 * 1000)
                .setConnectionRequestTimeout(30 * 1000)
                .setSocketTimeout(30 * 1000)                
                .build();
        } else{
            this.config = RequestConfig.custom()
                    .setConnectTimeout(6 * 1000)
                    .setConnectionRequestTimeout(30 * 1000)
                    .setSocketTimeout(60 * 1000)                
                    .build();           
        }
        this.context = HttpClientContext.create(); 
    

    对 UI 使用处理程序和回调,这样您就可以显示任何您想要的警报对话框

    在你有 '..client.exec(request$Type)' 的可运行环境中

            if(httprc < HttpStatus.SC_METHOD_NOT_ALLOWED){
    
                Log.d(TAG, "entityTYP " +response.getEntity().getClass().getName());
                processEntity(response.getEntity());
                response.close();
            }else{
                Log.d(TAG, "ERR httprc " +httprc);
                throw new IOException("httprc " +httprc +" on " +method);
                }                               
            this.context.getConnection().close();
    } catch (Exception e) {  // this will catch your 'socketException'
       // catch and use the looper to direct to the desired UI thread handle
        handler0.sendMessage(Message.obtain(handler,
                HttpConnection.DID_ERROR, e.getMessage()));
    }
    

    回到 UI 线程,您可以根据需要控制尽可能多的差异处理程序的警报....

               handler0 = new Handler() {
                   public void handleMessage(Message message) {
                     switch (message.what) {
                     case HttpConnection.DID_START: {
                       break;
                     }
                     case HttpConnection.DID_SUCCEED: {                                          
                       break;
                     }
                     case HttpConnection.DID_ERROR: {
                           toggleSpin(false);
                           cleanup();
                        //have access to orig message.obj here as well
                           Toast.makeText(Speech_API_Activity.this, getResources().getString(R.string.heroku_msg_mux),
                                Toast.LENGTH_SHORT).show();
                           break;
                         }
    

    如有必要,您可以按域建立差异超时配置文件。使用其他 2 个 httpclient 包来学习所有构建器的东西和配置东西需要时间,但是,这可能是值得的,因为你可以将它设置为做任何你想做的事情,并且你可以完全控制异常和你想要的东西路由回 UI。

    【讨论】:

      【解决方案4】:
      • 你在用从方法返回的 HttpPost 对象做什么 发布数据(...) ?这可能是需要考虑的原因之一。注意那里 是两个线程,一个是主线程,另一个是您生成的 以上。

      • 在 finally 块中,您需要关闭 httpClient 和
        等资源 响应以及完全清空响应输入流。请参阅示例以供参考。

      • 在 UrlEncodedFormEntity 上显式设置字符集,可能是 UTF-8

      【讨论】:

        【解决方案5】:

        尝试使用这个

        public static String WebserviceResponseHandle(String url,
                List<NameValuePair> namvaluePair) {
            String result = null;
            try {
                HttpParams httpParams = new BasicHttpParams();
                HttpConnectionParams.setConnectionTimeout(httpParams, 10000);
                HttpConnectionParams.setSoTimeout(httpParams, 10000);
                HttpClient client = new DefaultHttpClient(httpParams);
                HttpPost httppost = new HttpPost(url);
                httppost.setEntity(new UrlEncodedFormEntity(namvaluePair));
                HttpResponse response = client.execute(httppost);
                HttpEntity entity = response.getEntity();
        
                // If the response does not enclose an entity, there is no need
                if (entity != null) {
                    InputStream instream = entity.getContent();
                    result = convertStreamToString(instream);
        
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        }
        
        
            private static String convertStreamToString(InputStream is) {
            /*
             * To convert the InputStream to String we use the
             * BufferedReader.readLine() method. We iterate until the BufferedReader
             * return null which means there's no more data to read. Each line will
             * appended to a StringBuilder and returned as String.
             */
            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
            StringBuilder sb = new StringBuilder();
        
            String line = null;
            try {
                while ((line = reader.readLine()) != null) {
                    sb.append(line + "\n");
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return sb.toString();
        }
        

        【讨论】: