6

Firstly I know what your thinking, it's a strange question and it seems like it cannot be true but here me out on this one...

The project is a project which sends an array of bytes over a socket and also receives data from a socket. I am currently working on protection. I want to make sure that the user knows when the socket can't connect for whatever reason... You can view the connection code below.

So I have an android app which connects to a executable on a Windows Server computer in a office elsewhere in the world. The socket connects via the usual IP address and port number.

I am testing the app using a Android 5.0 (lollipop) phone...

Now enough of the boring stuff: If I close the server and turn it off completely and test this on wi-fi then the socket fails - which is correct. It cannot connect, and it will throw a SocketException. Which is fine and is working as expected. Now if I were to turn wi-fi off and use mobile data (I use o2 here in the sunny United Kingdom!) then an issue arises, using the same code, with the server still turned off, I do not throw a SocketException, instead the code simple thinks it has connected on a socket. The connection thread is below.

Thread StartConnection = new Thread()
        {
            @Override
            public void run()
            {

                int dstPort = 10600;

                socket = new Socket();
                try
                {
                    ipaddress = InetAddress.getByName(dstName);
                }
                catch (UnknownHostException uhe)
                {
                    uhe.printStackTrace();
                }

                j = new InetSocketAddress(IPString , dstPort);

                try
                {
                        socket.setKeepAlive(true);
                        socket.setReuseAddress(true);
                        socket.setTcpNoDelay(true);
                        socket.setSoTimeout(5000);
                        socket.connect(j, 5000);
                        connected = true;
                        outputstream = socket.getOutputStream();
                        DataThread();
                        SocketError = false;
                }
                catch (SocketException se)
                {
                    se.printStackTrace();
                }
                catch (IllegalArgumentException iae)
                {
                    iae.printStackTrace();
                }
                catch (IOException ioe)
                {
                    ioe.printStackTrace();
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        };
        StartConnection.start();

socket.connect(j, 5000); 

works fine no socket errors. No timeouts either.

outputstream = socket.getOutputStream(); 

also works. I get a result of "java.net.PlainSocketImpl$PlainSocketOutputStream@48e6f6e" which is also fine.

socket.getInputStream() returns "java.net.PlainSocketImpl$PlainSocketInputStream@1ea305a5"

apmartin1991
  • 3,064
  • 1
  • 23
  • 44
  • Mobile connections often use transparent proxies (you don't have to specify them and can not bypass them). May be the one from O2 is not correctly implemented for the case of non-open ports? – Robert Feb 23 '15 at 15:18
  • `the code simple thinks it has connected on a socket`. Maybe. But somehow/somewher you will get errors. Where? `socket.connect(j, 5000);` Does it timeout then? `connected = true;` So that is not true then. `outputstream = socket.getOutputStream();` What do you get here then? a null? Please tell better the flow. – greenapps Feb 23 '15 at 16:09
  • socket.connect(j, 5000); works fine no socket errors. No timeouts either. outputstream = socket.getOutputStream(); also works. I get a result of "java.net.PlainSocketImpl$PlainSocketOutputStream@48e6f6e" which is also fine. socket.getInputStream() returns "java.net.PlainSocketImpl$PlainSocketInputStream@1ea305a5" – apmartin1991 Feb 23 '15 at 16:25
  • @apmartin1991 Please put a breakpoint at the next line after the `connect` call. Also breakpoints at every possible exception. As they've told you, that method either blocks or throws exception. – Mister Smith Feb 24 '15 at 12:20
  • It throws no exception Mister Smith. It simple connects even though there is nothing to connect too. Socket.connect works .... If i was to use an Ip of "182.976.234.543" which does not exist then it still connects.... – apmartin1991 Feb 24 '15 at 12:21
  • I tried to reproduce the same [in india], but could not. I am getting a timeout exception. Refer to the code at [link](http://pastebin.com/B7PeBiRA). Kindly let me know if I am doing something wrong with the code. – Rahul Shukla Mar 05 '15 at 13:25
  • Can't reproduce this here. Connecting to a random non-existing IP always times out after the specified timeout (5s), on wifi and 3g. Connecting to "182.976.234.543" yields UnknownHostException, because it's not an IP at all, it's parsed as a hostname. – ci_ Mar 05 '15 at 14:58

2 Answers2

3

This is interesting. I have been testing and can replicate the behaviour you describe.

Firstly, the highest voted answer here provides a good discussion on the Java Socket API:

Java socket API: How to tell if a connection has been closed?

From this, I wondered whether attempting to read a byte from the socket would allow us to check if it is really open

// Endpoint that does not exist
Socket socket = new Socket("1.2.3.4", 1234);
InputStream tmpIn = socket.getInputStream();
int result = tmpIn.read();
Log.i("SocketTest", "read() result: " + result);

In the case that the endpoint is invalid, I have found that the call to read() returns -1 after approximately 10 seconds.

In the case that there is a listener on the IP and port, the call to read() seems to block indefinitely until something happens, such as actually receiving data from the server, or losing internet connectivity etc.

Based on this difference, I have wrapped this test up into a new class:

public class VerifiedSocket extends Socket {

    public VerifiedSocket(String ip, int port, int millisecondTimeout) throws UnknownHostException, IOException{
        super(ip,port);

        ReadThread tryRead = new ReadThread();      
        tryRead.start();
        try {
            tryRead.join(millisecondTimeout);
        } catch (InterruptedException e) {
        }
        if(tryRead.getReadValue()==-1){
            // We read -1 so assume endpoint invalid
            throw new IOException("Endpoint is invalid");
        }       
    }

    private class ReadThread extends Thread{
        private int readValue = 512; // Impossible byte value

        @Override
        public void run(){              
            try {
                InputStream input = getInputStream();
                readValue = input.read();
            } catch (IOException e) {                   
            }               
        }

        public int getReadValue(){
            return readValue;
        }
    }       
}

And then tested it as follows:

try {
    VerifiedSocket socket = new VerifiedSocket("1.2.3.4", 1234, 20000);
    Log.i("SocketTest", "Endpoint is valid");
    socket.close();
} catch (UnknownHostException e) {      
    Log.e("SocketTest", e.getLocalizedMessage());
} catch (IOException e) {       
    Log.e("SocketTest", e.getLocalizedMessage());
}

If I use an invalid endpoint on Wifi, the constructor of the VerifiedSocket throws an exception with a timeout. If I use an invalid endpoint on mobile data, it throws the IOException with the 'Endpoint is invalid' message. Not perfect, but hopefully it might help you.

Community
  • 1
  • 1
Gary Wright
  • 2,441
  • 1
  • 20
  • 32
  • Hi Gary, first of all, fantastic answer, really well worded with a great example. I also noticed that read() would return -1 too which is something that I did look at doing. However this would have been a work around so I was hoping for something better within the API. I will be using your code shortly and will report back if it does not work as expected. Many thanks for your well worded, well explained reply! :) – apmartin1991 Mar 05 '15 at 15:35
  • Thanks, I hope it works for you. If you have control over the server side application, I would suggest also including some kind acknowledged 'login' too, for completeness. – Gary Wright Mar 05 '15 at 15:39
  • We do indeed, Yeah, we have our own acknowledgment packets for all packets being sent with retries if a packet fails so we have that side covered, we just wanted to make sure that everything was covered really. – apmartin1991 Mar 05 '15 at 16:21
-1

If the environment where you are running the code does not behave as you need (from whatever reason, Robert has written one), you may resolve the ill-behaving environment with bringing a new abstraction.

You can do many things to resolve it; here is one solution - implement a wrapper on top of used protocol (java.net.Socket) which will extend Socket class and override the methods connect()/getInputStream()/getOutputStream()/whatever-method-you-need() the way that the code first tries to really contact the server before it returns a valid stream.

In the wrapper, sends couple of bytes (a client key) to the server upon which the server responds with an answer (a server key). Keep the keys in secret, only client and server will know the keys. If the server does not respond withing a timeout, throw an exception.

private class MySocket extends Socket {
    Socket inner;
    MySocket(Socket inner) {
        this.inner = inner;
    }

    @Override
    InputStream getInputStream() {
        // do your ping here
        // if ping fails, thrown an exception
        return inner.getInputStream();
    }

    ... override additional methods as you need
}

You can use this ping pattern in all the places to detect the connection got lost.

I know you are kind of doing what the underlying layer is supposed to be doing... but if it's not doing what you are expecting, what can you do?

pepan
  • 678
  • 6
  • 11
  • This kind of approach does rely on having control over the server code as well as the client code – Gary Wright Mar 03 '15 at 14:47
  • That's what I understood from the question; the author also implements the server side. But I could be wrong. – pepan Mar 03 '15 at 15:57