Can't read socket InputStream on Jelly Bean
Asked Answered
C

7

20

I have a TCP socket connection which works well on Android 2.3 but now facing some problems on Android 4.1. The problem is that InputStream.read() method always returns -1 (without blocking), like the connection is closed.

Creating socket:

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

Retrieving input and output streams and writing some initial data:

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

Then, this condition always passes without blocking:

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

This code is running in a background thread. And it was working on Gingerbread.

Tried to use InputStreamReader and OutputStreamWriter instead of direct streams - no effect.

Cumulostratus answered 1/3, 2013 at 9:10 Comment(15)
Did you check the logcat output and if the socket is bound? Did you set the correct permission in the manifest uses-permission android:name="android.permission.INTERNET.Merino
Yes, no exceptions. The permission is present.Cumulostratus
I think that something was changed in networking or streams logic since ICS, but cant guess what exactly. Previously there were similar issue with XmlPullParser, which stopped working with InputStream, but kindly accepted InputStreamReader instead: #11190994Cumulostratus
Ok, so your code is running in a background thread and you don't get NetworkOnMainThreadException. Sorry, can't help youMerino
did you found any solution to this problem I have the exact same issueBarrel
@AashishVirendraKBhatnagar I found a comment on a similar question that may help you: the problem was in my router.when i create portable hots pot from my s3 it worked perfectly.thanks for your attention.Oeo
that doesn't make sense to me how can a internet connection make such an impact.Barrel
@AashishVirendraKBhatnagar I don't know how. But this question poses a problem similar to yours: Android socket run in gingerbread but not in jelly bean. The comment I posted came from this thread.Oeo
that's correct but it really doesn't make any sense to meBarrel
We need more input to solve this issue. Please prepare an example project. In 4.x networking is forbidden on the main thread but that is indicated by an exception, not a return value of -1. -1 always means the connection was closed.Brittani
@AashishVirendraKBhatnagar can you post link an example project that reproduces this issue?Brittani
sure I will try to do that.Barrel
@AashishVirendraKBhatnagar all this looks like an issue I had some months ago. I've put the link to the bug report in an answer here. Let me know if it helped somehow.Colonial
Sorry guys for not responding, this issue is no more relevant for me, because now I'm not working on the project where it occured. Nonetheless I'll try to check your answers as soon as possible. Also, let me know if you've checked and can approve any of the solutions.Cumulostratus
-1 does mean EOS, and it shouldn't block. The peer has clearly closed the connection. When you get the -1 you should close your socket and stop using it.Prospector
C
1

I have seen that very same error before, although this answer might look offtopic give it a chance and let me know if it worked, for some reason sockets are having strange behavior on jellybean even when they were working completely fine in lower android versions, the way I fixed this issue was to move the targetSdkVersion to jelly bean as well as the Project Build Target under Android properties of the project, didn't modify one single line of code, just that, and for some reason it does the trick...

Hope this helps.

Regards!

Cassandry answered 13/9, 2013 at 16:13 Comment(0)
C
0

I had some similar issue where the inputStream.read() returned -1 and I did not get any Exception. In fact the server was down and the connection broken. I didn't test it with different versions, only with 4.0.

Here's the Google Bug Report about this behavior.

Unfortunately status of the bug seems to be 'closed' as not reproduceable.

My work around was to interpret the -1 as a close of the socket and an unreachable server. When you try to reconnect, you get the right errors.

Colonial answered 13/9, 2013 at 16:55 Comment(2)
that's fine I know this may happen when server has no data to send but if that's the case gingerbread and iPhone must not recieve it eitherBarrel
For me this looks like a Timing issue. So it's very difficult to reproduce. In fact, sometimes it works (throw exception when server connection is lost), sometimes not. That was the moment I stopped investigating and built my work around. What might be worth to check is, if newer Androids loose the Server connection more often and maybe get more often in this condition. For now I would go with the work around: -1 forces a reconnect to the server.Colonial
B
0

Friend,

try inputStream.readLine(); (i.e) DataInputStream.readLine(); (Deprecated method)

this worked for me...

Biotin answered 25/10, 2013 at 4:39 Comment(1)
Non-existent method. If you mean DataInputStream.readLine() you should say so.Prospector
D
0

I have had a similar problem and fixed it with a workaround like this

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");
    }
}
Deaminate answered 25/10, 2013 at 7:44 Comment(0)
V
0

Using BufferedReader and PrintWriter works on all versions for me and is extremely convenient for sending and receiving anything you want (even JSON strings) via any communication protocol. Try saving them as member variables when starting your background thread like this:

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

For asynchronous communication your background thread might then look like this:

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

Use another background thread to send messages:

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

Also, you will have to close the streams from outside to make sure readLine doesn't block forever:

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

Now, since you're using TCP sockets it may happen that the socket is actually dead and readLine is still blocking. You have to detect that and close the streams just like above. For that, you will have to add another thread (oh well) that periodically sends keep-alive-messages. If no message was received from the remote device for X seconds, it has to close the streams.

This whole approach makes sure the socket is closed and all threads finish at all circumstances. Of course you can make the communication synchronous, if that is what you need, by removing the sender-thread and including println() inside the reader-thread instead. I hope that helps you (even though the answer comes 8 months late).

Veranda answered 8/11, 2013 at 13:14 Comment(0)
N
0

Try this code -

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

    }

};

It works for me.

Novercal answered 13/12, 2013 at 8:18 Comment(0)
N
-1

You should use Apache Commons IO: http://commons.apache.org/proper/commons-io/

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

Necropsy answered 12/9, 2013 at 8:15 Comment(1)
Adding largish libraries like Commons IO to an Android app bloats the app and exposes your app to inefficient code in the library. Unless you're using a large percentage of the libraries features I would advise against bringing it in.Daddylonglegs

© 2022 - 2024 — McMap. All rights reserved.