Не удается прочитать сокет InputStream на Jelly Bean

У меня есть подключение к сокету TCP, которое хорошо работает на Android 2.3, но теперь сталкивается с некоторыми проблемами на Android 4.1. Проблема в том, что метод InputStream.read() всегда возвращает -1 (без блокировки), как будто соединение закрыто.

Создание сокета:

SocketFactory socketFactory = SocketFactory.getDefault();
Socket socket = socketFactory.createSocket("c.whatsapp.net", 5222);
socket.setSoTimeout(3*60*1000);
socket.setTcpNoDelay(true);

Получение входных и выходных потоков и запись некоторых исходных данных:

InputStream inputStream = new BufferedInputStream(socket.getInputStream());
OutputStream outputStream = new BufferedOutputStream(socket.getOutputStream());

outputStream.write(87);
outputStream.write(65);
outputStream.write(1);
outputStream.write(2);
outputStream.flush();

Тогда это условие всегда проходит без блокировки:

int c = inputStream.read();
if (c < 0) {
    Log.d(TAG, "End of stream");
}

Этот код выполняется в фоновом потоке. И это работало над Gingerbread.

Пробовал использовать InputStreamReader и OutputStreamWriter вместо прямых потоков - безрезультатно.


person Alexander Sukharev    schedule 01.03.2013    source источник
comment
Вы проверяли вывод logcat и привязан ли сокет? Вы установили правильное разрешение в манифесте uses-permission android:name="android.permission.INTERNET.   -  person faceman    schedule 01.03.2013
comment
Да без исключений. Разрешение присутствует.   -  person Alexander Sukharev    schedule 01.03.2013
comment
Я думаю, что что-то изменилось в сетевой или потоковой логике со времен ICS, но не могу понять, что именно. Ранее была похожая проблема с XmlPullParser, которая перестала работать с InputStream, но вместо этого любезно приняла InputStreamReader: заголовок stackoverflow.com/questions/11190494/   -  person Alexander Sukharev    schedule 01.03.2013
comment
Итак, ваш код выполняется в фоновом потоке, и вы не получаете NetworkOnMainThreadException. Извините, не могу вам помочь   -  person faceman    schedule 01.03.2013
comment
вы нашли решение этой проблемы у меня точно такая же проблема   -  person Aashish Bhatnagar    schedule 07.09.2013
comment
@AashishVirendraKBhatnagar Я нашел комментарий по похожему вопросу, который может вам помочь: the problem was in my router.when i create portable hots pot from my s3 it worked perfectly.thanks for your attention.   -  person Vikram    schedule 07.09.2013
comment
это не имеет для меня смысла, как интернет-соединение может оказать такое влияние.   -  person Aashish Bhatnagar    schedule 07.09.2013
comment
@AashishVirendraKBhatnagar Я не знаю как. Но этот вопрос создает проблему, аналогичную вашей: Android socket бегать в имбирных пряниках, но не в желейных бобах. Комментарий, который я оставил, взят из этой ветки.   -  person Vikram    schedule 07.09.2013
comment
это правильно, но это действительно не имеет никакого смысла для меня   -  person Aashish Bhatnagar    schedule 09.09.2013
comment
Нам нужно больше информации, чтобы решить эту проблему. Пожалуйста, подготовьте пример проекта. В 4.x работа в сети запрещена в основном потоке, но на это указывает исключение, а не возвращаемое значение -1. -1 всегда означает, что соединение было закрыто.   -  person allprog    schedule 12.09.2013
comment
@AashishVirendraKBhatnagar, можете ли вы опубликовать ссылку на пример проекта, который воспроизводит эту проблему?   -  person allprog    schedule 12.09.2013
comment
обязательно попробую сделать.   -  person Aashish Bhatnagar    schedule 13.09.2013
comment
@AashishVirendraKBhatnagar все это похоже на проблему, с которой я столкнулся несколько месяцев назад. Я разместил ссылку на отчет об ошибке в ответе здесь. Дайте мне знать, если это как-то помогло.   -  person jboi    schedule 13.09.2013
comment
Извините, ребята, что не отвечаю, этот вопрос для меня более не актуален, так как сейчас я не работаю над проектом, где это произошло. Тем не менее, я постараюсь проверить ваши ответы как можно скорее. Кроме того, дайте мне знать, если вы проверили и можете одобрить какое-либо из решений.   -  person Alexander Sukharev    schedule 30.10.2013
comment
-1 означает EOS, и он не должен блокироваться. Пир явно закрыл соединение. Когда вы получите -1, вы должны закрыть сокет и прекратить его использовать.   -  person user207421    schedule 13.11.2013


Ответы (7)


Я видел ту же самую ошибку раньше, хотя этот ответ может выглядеть не по теме, дайте ему шанс и дайте мне знать, если он сработает, по какой-то причине сокеты ведут себя странно на jellybean, даже когда они работали совершенно нормально в более ранних версиях Android, способ, которым я исправил эту проблему, состоял в том, чтобы переместить targetSdkVersion в jelly bean, а также цель сборки проекта в свойствах Android проекта, не изменяя ни одной строки кода, только это, и по какой-то причине это делает свое дело. .

Надеюсь это поможет.

С уважением!

person Martin Cazares    schedule 13.09.2013

У меня была похожая проблема, когда inputStream.read() вернул -1, и я не получил никаких исключений. На самом деле сервер был отключен, а соединение прервано. С разными версиями не тестировал, только с 4.0.

Вот отчет об ошибках Google об этом поведении.

К сожалению, статус ошибки кажется «закрытым» как невоспроизводимым.

Моя работа заключалась в том, чтобы интерпретировать -1 как закрытие сокета и недостижимый сервер. Когда вы пытаетесь переподключиться, вы получаете правильные ошибки.

person jboi    schedule 13.09.2013
comment
это нормально, я знаю, что это может произойти, когда у сервера нет данных для отправки, но в этом случае имбирный пряник и iPhone не должны их получать. - person Aashish Bhatnagar; 14.09.2013
comment
Для меня это выглядит как проблема синхронизации. Так что это очень трудно воспроизвести. На самом деле, иногда это работает (генерировать исключение при потере связи с сервером), иногда нет. Это был момент, когда я прекратил расследование и построил свою работу вокруг. Что, возможно, стоит проверить, так это то, что новые Android-устройства чаще теряют соединение с сервером и, возможно, чаще становятся в этом состоянии. На данный момент я бы пошел с обходным путем: -1 принудительно переподключается к серверу. - person jboi; 14.09.2013

У меня была аналогичная проблема, и я исправил ее с помощью обходного пути, подобного этому

private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);

private static class WatchDog implements Runnable{
    private Thread thread = Thread.currentThread();

    public void run() {
        Log.d(LOG_TAG, "Interrupting read due to timeout");
        thread.interrupt();
    }
}

private void read(InputStream in, ByteBuffer bb, long waitTime) throws IOException {
    int startingPos = bb.position();
    long timeout = System.currentTimeMillis() + RESPONSE_TIMEOUT;


    ScheduledFuture<?> watchdogFuture = executor.schedule(new WatchDog(), RESPONSE_TIMEOUT, TimeUnit.MILLISECONDS);
    try {
        while(System.currentTimeMillis() < timeout && bb.hasRemaining()){ //workaround fixing timeout after 1ms
            try{
                int read = in.read(bb.array(), bb.position(), bb.remaining());
                if(read > 0){
                    bb.position(bb.position()+read);
                }
            } catch(SocketTimeoutException e){}
            if(bb.hasRemaining()){
                Thread.sleep(5);
            }
        }
        watchdogFuture.cancel(true);
    } catch (InterruptedException e) {}


    if(bb.hasRemaining()){
        throw new SocketTimeoutException("Unable to read requested bytes: " 
                + (bb.position()-startingPos) + "/" +  (bb.limit()-startingPos)
                + " after " + (System.currentTimeMillis() - timeout + RESPONSE_TIMEOUT) + "ms");
    }
}
person Jan    schedule 25.10.2013

Использование BufferedReader и PrintWriter у меня работает на всех версиях и чрезвычайно удобно для отправки и получения чего угодно (даже строк JSON) по любому протоколу связи. Попробуйте сохранить их как переменные-члены при запуске фонового потока следующим образом:

mInput = new BufferedReader(new InputStreamReader(
            socket.getInputStream()));
mOutput = new PrintWriter(new BufferedWriter(
            new OutputStreamWriter(socket.getOutputStream())), true);

Для асинхронной связи ваш фоновый поток может выглядеть так:

@Override
public final void run() {
    while (!Thread.currentThread().isInterrupted()) {
        if (mInput == null) {
            break;
        }
        String message = null;
        try {
            message = mInput.readLine();
        } catch (IOException e) {
            // handle the exception as you like
            break;
        }
        if (Thread.currentThread().isInterrupted()) {
            // thread was interrupted while reading
            break;
        } else if (message != null) {
            // handle the message as you like
        }
    }
}

Используйте другой фоновый поток для отправки сообщений:

@Override
public void run() {
    if (mOutput != null) {
        mOutput.println(<message to be );
        if (mOutput == null) {
            // the above thread was interrupted while writing
        } else if (!mOutput.checkError()) {
            // everything went fine
        } else {
            // handle the exception
        }
    }
}

Кроме того, вам придется закрыть потоки извне, чтобы readLine не блокировался навсегда:

try {
    mOutput.close();
    mInput.close();
    mOutput = null;
    mInput = null;
} catch (IOException e) {
    // log the exception
}

Теперь, поскольку вы используете сокеты TCP, может случиться так, что сокет на самом деле мертв, а readLine все еще блокируется. Вы должны обнаружить это и закрыть потоки, как указано выше. Для этого вам придется добавить еще один поток (да ладно), который периодически отправляет сообщения проверки активности. Если в течение X секунд от удаленного устройства не было получено ни одного сообщения, оно должно закрыть потоки.

Весь этот подход обеспечивает закрытие сокета и завершение всех потоков при любых обстоятельствах. Конечно, вы можете сделать связь синхронной, если это то, что вам нужно, удалив поток отправителя и вместо этого включив println() внутри потока чтения. Я надеюсь, что это поможет вам (хотя ответ приходит с опозданием на 8 месяцев).

person 0101100101    schedule 08.11.2013

друг,

попробуйте inputStream.readLine(); (т.е.) DataInputStream.readLine(); (устаревший метод)

это сработало для меня...

person sais    schedule 25.10.2013
comment
Несуществующий метод. Если вы имеете в виду DataInputStream.readLine(), вы должны так и сказать. - person user207421; 13.11.2013

Попробуйте этот код -

Runnable runnable = new Runnable() {

    @Override
    public void run() {

        synchronized (this) {
            Socket s = null;
            String inMsg = null, msg2 = null;
            try {
                try {
                    s = new Socket(server, port);
                } catch (Exception e) {
                    return;
                }
                BufferedReader in = new BufferedReader(
                        new InputStreamReader(s.getInputStream()));
                BufferedWriter out = new BufferedWriter(
                        new OutputStreamWriter(s.getOutputStream()));
                try {
                    inMsg = in.readLine()
                            + System.getProperty("line.separator");
                } catch (Exception e) {
                    return;
                }

                out.write(message + "\n\r");
                out.flush();
                try {
                    msg2 = in.readLine();
                    if (msg2 == null) {
                        return;
                    }
                } catch (Exception e) {
                    return;
                }
                out.close();
                s.close();
            } catch (Exception e) {
                return;
            }
        }

    }

};

Меня устраивает.

person unflagged.destination    schedule 13.12.2013

Вы должны использовать Apache Commons IO: http://commons.apache.org/proper/commons-io/

См. IOUtils.copy() http://commons.apache.org/proper/commons-io/javadocs/api-release/index.html?org/apache/commons/io/package-summary.html

person Felipe Lima    schedule 12.09.2013
comment
Добавление больших библиотек, таких как Commons IO, в приложение для Android раздувает приложение и подвергает ваше приложение неэффективному коду в библиотеке. Если вы не используете большой процент функций библиотек, я бы не советовал их вводить. - person Matt Accola; 01.12.2013