问题背景

最近在研究Socket编程的时候,发现书上有一个代码片段下:

class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8088);
        Socket socket = serverSocket.accept();
        InputStream inputStream = socket.getInputStream();
        System.out.println("A=" + inputStream.available());
        socket.close();
        serverSocket.close();
    }
}
class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("localhost", 8088);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("abcdefg".getBytes());
        socket.close();
    }
}

在书上贴的服务端输出是 A=7 ,也就是 available() 返回了 7,但是我在本地按照 先运行Server 后运行 Client 的方式一直得到的都是 0,这到底是为什么?

原因剖析

首先先来看下 available() 方法的语义:

Returns an estimate of the number of bytes that can be read from this input stream without blocking by the next invocation of a method for this input stream.
The available method for class InputStream always returns 0.
This method should be overridden by subclasses.

根据这个描述我们大概知道这个方法主要是 返回此输入流在不受阻塞情况下能读取的字节数,在父类默认实现是一直返回0,具体的由各个子类去覆写实现。

对于我们当前的用例,实现的子类是 SocketInputStream,对应的实现如下:

/**
 * Returns the number of bytes that can be read without blocking.
 * 这里要求返回的值是 非阻塞 的
 */
protected synchronized int available() throws IOException {
    if (isClosedOrPending()) {
        throw new IOException("Stream closed.");
    }
    /*
     * If connection has been reset or shut down for input, then return 0
     * to indicate there are no buffered bytes.
     */
    if (isConnectionReset() || shut_rd) {
        return 0;
    }

    /*
     * If no bytes available and we were previously notified
     * of a connection reset then we move to the reset state.
     *
     * If are notified of a connection reset then check
     * again if there are bytes buffered on the socket.
     */
    int n = 0;
    try {
        n = socketAvailable(); // 这里是返回此时socket返回的下一次可用的值
        if (n == 0 && isConnectionResetPending()) {
            setConnectionReset();
        }
    } catch (ConnectionResetException exc1) {
        setConnectionResetPending();
        try {
            n = socketAvailable();
            if (n == 0) {
                setConnectionReset();
            }
        } catch (ConnectionResetException exc2) {
        }
    }
    return n;
}

所有这些方法的注释中都明确的强调了此方法读取的可用的数据是非阻塞的,这明显和我们socket流是不相符的,因为socket都是会阻塞等待连接,并且连接上之后该方法不会阻塞等待客户端write数据,会直接读取此时通道中的可用数据,当服务端此方法比客户端write方法先执行时,服务端返回0也能解释了,就是服务端一开始属于阻塞等待,客户端在连上之后还未将字符串写到服务端,服务端就开始获取数据,这时候即为0

针对这个场景,如果让让该值能够显示为7,可以通过下面两种方式复现:

  • debug模式:即让客户端连接上服务端之后,服务的端点一直在 System.out.println("A=" + inputStream.available()); 之前等待,让客户端先执行完 outputStream.write("abcdefg".getBytes());,此时 inputStream.available() 就将获取到此时客户端写过来的字符串

  • 通过增加read方法来等待客户端执行write方法,因为在读取时如果没有内容read()方法是会受阻的,所以从socket初始化的输入流的available也是为零的,所以要read一字节后再使用,此时说明客户端已经写数据到服务端了,后面再用available()读取剩余的字节数,总的可用的字节数就等于 available + 1

    class Server {
        public static void main(String[] args) throws IOException {
            ServerSocket serverSocket = new ServerSocket(8088);
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            int firstLength = inputStream.read(); // 阻塞等待客户端write数据到服务端
            System.out.println("A=" + inputStream.available());
            socket.close();
            serverSocket.close();
        }
    }
    

更多推荐

网络流中的 InputStream.available() 为什么会一直等于 0