安卓错误:java.net.SocketException:Socket closed

20

我每周在我的崩溃日志中看到这个错误出现了数百次,但我已经花费了几个星期的时间来追踪此错误,但没有成功。 我无法在我的任何设备上重现它。以下是堆栈跟踪:

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;
    }
任何对于最终解决此问题的帮助都会非常感激!

11
调试看不见的代码很困难。 - takendarkk
抱歉,我加了一些代码来查看。 - D-Nice
另一个线程正在关闭套接字,而此线程在read()中被阻塞。你有没有偶然重用HTTPClient? - user207421
不,没有其他线程关闭套接字。我们在应用程序中每次想发出请求时都使用postData函数。 - D-Nice
2
我猜你的描述意味着你的应用程序用户看到了这个崩溃,而你无法重现它。如果是这种情况,可能他们只是网络连接不好,你需要更优雅地处理这些错误。在测试应用程序时,请尝试在应用程序运行时切断互联网连接,看看是否会出现类似的情况。如果是时间问题,在失败的代码行之前加上一个延迟,这样你就可以确保在继续之前切断数据连接。 - blh83
5个回答

17

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

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

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


6

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

另一方面,如果实际上是在服务器端发生了这种情况,我建议您实现重试-3次重试是常见做法,通常足够。


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

1
你目前正在接受客户端库配置的默认设置。也许你想更好地控制Httpclient库,特别是关于套接字超时设置。不要等待服务器做出意外的操作。设置比默认时间更短的超时时间,并以一种对用户有意义的方式控制错误,“稍后重试”消息...如果您使用默认的Android Httpclient,则可能需要查看与较新的Apache客户端版本保持一致的替代方案...

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

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

通用后台异步客户端

请注意,使用任何一种方法,您都可以考虑(在Wifi上)或(在4G上)您可以拨入详细超时配置文件,其中您可以使用以下代码控制超时时间:

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;
                     }

如果需要,您可以按域名建立不同的超时配置文件。学习这两个httpclient包的所有构建和配置需要时间,但是这可能值得,因为您可以将其设置为执行您想要的任何操作,并且您完全控制异常和您想要路由回UI的内容。

1
  • 您是如何使用从postData(...)方法返回的HttpPost对象的?这可能是需要考虑的原因之一。请注意,此代码中有两个线程,一个主线程和一个您创建的线程。

  • 在finally块中,您需要关闭像httpClient和response这样的资源,并彻底清空response inputstream。可以参考示例。

  • 明确设置UrlEncodedFormEntity的字符集,例如UTF-8。


0

尝试使用这个

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();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接