关闭后写入后无法访问Socket InputStream的内容

时间:2018-07-29 13:49:03

标签: java sockets serversocket

我正在尝试读取刚刚关闭的套接字输入流的全部内容。但是,如果我尝试读取它通常可以正常工作,那么我将不断尝试同时写入该套接字的输出流。看起来好像在其中写入内容会“破坏”套接字的输入流,并使其无法读取剩余的内容。

以我做的这个小例子来说明我的问题:

@Test
public void socketTest() throws Throwable
{
    new Thread(() -> {
        try
        {
            ServerSocket serverSocket = new ServerSocket(5555);

            while(true)
            {

                //keep listening
                Socket socket = serverSocket.accept();

                InputStream in = socket.getInputStream();

                System.out.println(in.read());

                Thread.sleep(2000);

                try
                {
                    socket.getOutputStream().write(3);
                }
                catch(Throwable ex)
                {
                    System.out.println("Error writing");
                    ex.printStackTrace();
                }

                System.out.println(in.read());

                in.close();
                socket.close();
                System.exit(0);
            }
        }
        catch(Throwable ex)
        {
            System.out.println("Error reading");
            ex.printStackTrace();
        }
    }).start();

    Thread.sleep(100); //you might need this
    Socket socket = new Socket("localhost", 5555);
    OutputStream out = socket.getOutputStream();
    out.write(1);
    out.write(2);
    out.close();
    socket.close();

    synchronized(this)
    {
        wait();
    }
}

我得到输出:

1
java.net.SocketException: Software caused connection abort: recv failed
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
    at java.net.SocketInputStream.read(SocketInputStream.java:170)
    at java.net.SocketInputStream.read(SocketInputStream.java:141)
    at java.net.SocketInputStream.read(SocketInputStream.java:223)
    at net.jumpai.test.SocketTTest.lambda$socketTest$0(SocketTTest.java:55)
    at java.lang.Thread.run(Thread.java:745)
Error reading

但是,永远不会读取2,并且在流中写入3永远不会出错。我不在乎是否在流中写入会抛出异常,但是我想读一下2。有什么方法可以做到这一点?

我在Windows 7上使用jdk1.8.0_91。即使更新到jdk1.8.0_181(当前最新版本)之后,我也可以重现该问题。我还可以在另一台Java 1.8.0_111的Windows 7计算机上进行复制。

无法在ArchLinux open-jdk1.8.0_172上重现该错误,这是否意味着它是Java版本的错误?它似乎确实与Windows或至少与Oracle的实现有关。

1 个答案:

答案 0 :(得分:1)

看到这种行为可以在任何版本的2台Windows机器上轻松再现,而在任何Linux或Mac系统上却不容易再现,我怀疑这是Oracle JVM中的错误,而open-jdk并不存在。

此错误非常令人讨厌,如果有人可以发布解决方法,我将不胜感激。同时,我已经向Oracle提交了错误报告:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8208479

试图解决这个问题,我附带了此工具,可以检查您的系统是否有错误。

public static boolean hasOracleSocketBug()
{
    try
    {
        int port = 10000;

        while(!PortChecker.portAvailable(port))
            port++;

        ServerSocket serverSocket = new ServerSocket(port);

        int fPort = port;
        new Thread(() -> {
            try
            {
                Socket socket = new Socket("localhost", fPort);
                OutputStream out = socket.getOutputStream();
                out.write(1);
                out.write(2);
                out.close();
                socket.close();
            }
            catch(Exception ex)
            {
                try
                {
                    serverSocket.close();
                }
                catch(IOException ignored) {}
            }
        }).start();

        Socket socket = serverSocket.accept();

        InputStream in = socket.getInputStream();
        in.read();
        socket.getOutputStream().write(3);

        try
        {
            in.read();
        }
        catch(Exception ex)
        {
            socket.close();
            return true;
        }

        in.close();
        socket.close();
        return false;
    }
    catch(Throwable ignored)
    {
        return false;
    }
}

public static boolean portAvailable(int port)
{
    ServerSocket ss = null;
    try
    {
        ss = new ServerSocket(port);
        ss.setReuseAddress(true);
        return true;
    }
    catch(IOException ignored)
    {
        return false;
    }
    finally
    {
        if(ss != null)
        {
            try
            {
                ss.close();
            }
            catch(IOException ignored) {}
        }
    }
}