Java Socket InputStream read方法阻塞

在使用Socket发送信息后,发现发完数据程序一直卡着,不会自动停止,也不知道哪有问题,找了下,发现read方法有点东西。
Snipaste_2020-08-31_12-02-22.png

References:

java.io.InputStream.read(byte[] b)方法的API描述有这么一段

Reads some number of bytes from the input stream and stores them into the buffer array b. The number of bytes actually read is returned as an integer. This method blocks until input data is available, end of file is detected, or an exception is thrown.

可以看到,除非读到EOF或者抛异常了,read方法会一直阻塞,直到有数据可以读。也就是说,在不抛异常的情况下,如果客户端不发送EOF,服务端的read方法就会一直阻塞。如果发了EOF,那么本次socket通信结束,这个流就无法继续使用了。

贴一段测试的代码,代码是上传文件的小Demo,服务器启动后等待客户端连接,客户端连接上会将D:\\tmp文件上传到服务端,服务端将文件保存为D:\\fileupload\\tmp,非常简单。

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8888);

        System.out.println("准备连接");
        Socket socket = serverSocket.accept();

        String parentPath = "D:\\fileupload\\";
        File file = new File(parentPath);
        if (!file.exists()) {
            file.mkdirs();
        }
        FileOutputStream fileOutputStream = new FileOutputStream(parentPath + "tmp");

        System.out.println("开始接收");
        InputStream socketInputStream = socket.getInputStream();
        byte[] readBuffer = new byte[1024];
        int readOffset = 0;
        while ((readOffset = socketInputStream.read(readBuffer)) != -1) {
            System.out.println(readOffset);
            fileOutputStream.write(readBuffer, 0, readOffset);
        }
        System.out.println("接收完毕");

        OutputStream socketOutputStream = socket.getOutputStream();
        socketOutputStream.write("传输完成".getBytes());
        System.out.println("响应完成");

        fileOutputStream.close();
        socketInputStream.close();
        socketOutputStream.close();
        socket.close();
        serverSocket.close();
    }
}
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8888);

        FileInputStream fileInputStream = new FileInputStream("D:\\tmp");

        System.out.println("开始上传");
        OutputStream socketOutputStream = socket.getOutputStream();
        byte[] writeBuffer = new byte[1024];
        int writeOffset = 0;
        while ((writeOffset = fileInputStream.read(writeBuffer)) != -1) {
            socketOutputStream.write(writeBuffer, 0, writeOffset);
        }
//        socket.shutdownOutput(); // 发送EOF, 如果不发送服务端的read就会block
        System.out.println("上传完成");

        InputStream socketInputStream = socket.getInputStream();
        byte[] readBuffer = new byte[1024];
        int readOffset = 0;
        while ((readOffset = socketInputStream.read(readBuffer)) != -1) {
            System.out.println("收到服务器响应:" + new String(readBuffer, 0, readOffset));
        }

        fileInputStream.close();
        socketOutputStream.close();
        socketInputStream.close();
        socket.close();
    }
}

在客户端发完文件后调用了socket.shutdownOutput();主动发送EOF结束,否则程序就会一直阻塞,只能强行中止。

对此,StackOverflow上的解释比较好,简单翻译下:

The underlying data source for some implementations of InputStream can signal that the end of the stream has been reached, and no more data will be sent. Until this signal is received, read operations on such a stream can block.

For example, an InputStream from a Socket socket will block, rather than returning EOF, until a TCP packet with the FIN flag set is received. When EOF is received from such a stream, you can be assured that all data sent on that socket has been reliably received, and you won't be able to read any more data. (If a blocking read results in an exception, on the other hand, some data may have been lost.)

Other streams, like those from a raw file or serial port, may lack a similar format or protocol to indicate that no more data will be available. Such streams can immediately return EOF (-1) rather than blocking when no data are currently available. In the absence of such a format or protocol, however, you can't be sure when the other side is done sending data.

InputStream某些实现的底层数据源可以发出信号,表示已经到达流的终点,将不再发送数据。在收到这个信号之前,对流的读取操作会阻塞。

例如,Socket的InputStream会阻塞,而不是返回EOF,直到收到一个设置了FIN标志的TCP数据包。当从流中接收到EOF时,可以保证在该套接字上发送的所有数据都已被可靠地接收,你将无法再读取任何数据。(另外,如果阻塞读取发生了异常,那么可能有数据丢失了)。

其他的流,比如那些来自原始文件或串行端口的流,可能缺乏类似的格式或协议来表明不会再有数据可用。当前没有数据可用时,这类流可以立即返回EOF(-1),而不是阻塞。但是,在没有这种格式或协议的情况下,你无法确定对方何时发送完数据。

所以服务端在没有收到明确的EOF是不能自作主张的关闭流或认为数据已经发送完成,因为网络的不可靠,任何操作都要明确的表示出来。

标签: none

仅有一条评论

  1. LiBloom LiBloom

    多谢,我也遇到了这个问题,发现自己对网络传输底层的知识还是薄弱。 ali-01.gif

添加新评论

ali-01.gifali-58.gifali-09.gifali-23.gifali-04.gifali-46.gifali-57.gifali-22.gifali-38.gifali-13.gifali-10.gifali-34.gifali-06.gifali-37.gifali-42.gifali-35.gifali-12.gifali-30.gifali-16.gifali-54.gifali-55.gifali-59.gif

加载中……