Android - 套接字已连接,尽管它无法连接

Android - Socket Connected even though it cannot

首先,我知道你在想什么,这是一个奇怪的问题,似乎这不可能是真的,但我想谈谈这个...

该项目是一个通过套接字发送字节数组并从套接字接收数据的项目。我目前正在从事保护工作。我想确保用户知道套接字何时因任何原因无法连接...您可以查看下面的连接代码。

所以我有一个 android 应用程序,它连接到世界其他地方办公室的 Windows 服务器计算机上的可执行文件。套接字通过常用的 IP 地址和端口号连接。

我正在使用 Android 5.0(棒棒糖)phone...

测试应用程序

现在够无聊的东西了: 如果我关闭服务器并完全关闭它并在 wi-fi 上测试它,那么套接字就会失败——这是正确的。它无法连接,并且会抛出 SocketException。这很好并且按预期工作。现在,如果我关闭 wi-fi 并使用移动数据(我在阳光明媚的英国使用 o2!),那么问题就出现了,使用相同的代码,服务器仍然关闭,我不会抛出 SocketException,相反,代码简单认为它已连接到套接字上。连接线程在下面。

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

工作正常,没有套接字错误。也没有超时。

outputstream = socket.getOutputStream(); 

也有效。我得到 "java.net.PlainSocketImpl$PlainSocketOutputStream@48e6f6e" 的结果,这也很好。

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

如果您所在的环境 运行 代码的行为不符合您的需要(无论出于何种原因,Robert 已经编写了一个代码),您可以通过引入新的抽象来解决行为不佳的环境。

你可以做很多事情来解决它;这是一个解决方案——在使用的协议 (java.net.Socket) 之上实现一个包装器,它将扩展 Socket class 并覆盖方法 connect()/getInputStream()/getOutputStream()/whatever-method-you -need() 代码在 returns 有效流之前首先尝试真正联系服务器的方式。

在包装器中,向服务器发送几个字节(一个客户端密钥),服务器在该服务器上响应一个答案(一个服务器密钥)。将密钥保密,只有客户端和服务器知道密钥。如果服务器没有超时响应,则抛出异常。

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
}

您可以在所有地方使用此 ping 模式来检测连接丢失。

我知道你正在做底层应该做的事情......但如果它没有做你期望的事情,你能做什么?

这很有趣。我一直在测试并且可以复制您描述的行为。

首先,这里投票最高的答案对 Java 套接字进行了很好的讨论 API:

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

据此,我想知道尝试从套接字读取一个字节是否允许我们检查它是否真的打开

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

在端点无效的情况下,我发现在大约 10 秒后对 read() returns -1 的调用。

在 IP 和端口上有侦听器的情况下,对 read() 的调用似乎会无限期地阻塞,直到发生某些事情,例如实际从服务器接收数据,或失去互联网连接等。

基于这个差异,我将这个测试打包成一个新的 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;
        }
    }       
}

然后测试如下:

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

如果我在 Wifi 上使用无效端点,VerifiedSocket 的构造函数会抛出超时异常。如果我在移动数据上使用无效端点,它会抛出带有 'Endpoint is invalid' 消息的 IOException。不完美,但希望它能对你有所帮助。