javasocketstcpobjectinputstream

Trans File use java socket, ObjectInputStream and ObjectOutputStream but blocked


Send file with socket BIO use Socket, ServerSocket, ObjectInputStream and ObjectOutputStream but blocked. The detail code is: Model:

@Data
@ToString
public class FileTransModel implements Serializable {
    private String fileName;
    private Long fileLength;
    private Integer status;
}

Client:

public class FileTransClient {

    private static final int BUFFER_SIZE = 1024 * 8;
    
    private static final byte[] BUFFER = new byte[BUFFER_SIZE];
    
    

    public static void main(String[] args) {
        try (Socket socket = new Socket("localhost", 1888);
             ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
             ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream())) {
            
            File file = new File("D:\\trans\\1.mp4");
            FileTransModel fileTransModel = new FileTransModel();
            fileTransModel.setFileName(file.getName());
            fileTransModel.setFileLength(file.length());
            oos.writeObject(fileTransModel);

            FileInputStream fileInputStream = new FileInputStream(file);
            BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
            int len;
            while ((len = bufferedInputStream.read(BUFFER)) != -1) {
                System.out.println(len);
                oos.write(BUFFER, 0, len);
                oos.flush();
            }
            System.out.println("file send over");
            FileTransModel model = (FileTransModel) ois.readObject();
            System.out.println(model);
            
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

Server:

public class FileTransServer {

    private static final int BUFFER_SIZE = 1024 * 8;

    private static final byte[] BUFFER = new byte[BUFFER_SIZE];

    public static void main(String[] args) {
        ServerSocket serverSocket;
        try {
            serverSocket = new ServerSocket(1888);
            System.out.println("Socket Server start on port: 1888");
            while(true) {
                Socket accept = serverSocket.accept();
                System.out.println("new file come!");
                new Thread(new Task(accept)).start();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    private static class Task implements Runnable {

        Socket socket;
        
        public Task(Socket socket) {
            this.socket = socket;
        }
        @Override
        public void run() {
            try {
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                FileTransModel fileTransModel = (FileTransModel) ois.readObject();
                System.out.println(fileTransModel);
                
                File file = new File("D:\\trans_rec2\\"+fileTransModel.getFileName());
                if (!file.exists()) {
                    file.createNewFile();
                }

                FileOutputStream fos = new FileOutputStream(file);
                BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fos);

                int len;
                len = ois.read(BUFFER);
                int size = 0;
                while (true) {
                    size += len;
                    System.out.println(len + ": " + size);
                    if (len == -1) {
                        break;
                    }
                    bufferedOutputStream.write(BUFFER, 0, len);
                    bufferedOutputStream.flush();
                    len = ois.read(BUFFER);
                }
                System.out.println("file write over");
                
                fileTransModel.setStatus(0);
                
                oos.writeObject(fileTransModel);
                System.out.println("file receive over");
                
            } catch (IOException | ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

and the result is : enter image description here Client had trans over, but Server is like that: enter image description here it cannot go on. how to fix it

I have tried to use DataInputStream and DataOutputStream, but the result is the same.


Solution

  • According to the documentation of read method in ObjectInputStream:

    This method will block until some input is available.

    So basically you are never getting len to be equal to -1 at server side. That's because of the sequence of events:

    1. Client sends the model and then the file bytes. Client then waits to receive model from server. ObjectInputStream is in blocking I/O mode here, so is readObject in the following snippet of your client side code:
      System.out.println("file send over");
      FileTransModel model = (FileTransModel) ois.readObject(); // Blocks here.
      
    2. Server receives the model. Then server receives buffers of a positive size until the other end is closed.
    3. But client is waiting for the model to be received, so it's not going to close.
    4. Since server is waiting blocked for more bytes or a -1 (assuming no IOException occurs) inside the loop, then it is not going to send the model back.

    As a result both sides are blocked waiting from each other.

    To solve this you can signify the end of file transmission in a way such that the server knows when to stop reading file bytes. Since the file contents can be arbitrary and its size is not pre-determined at server side, then you can send the file size before you begin sending its contents to the server. But the model already has this information, so your reading loop in the server side can be modified from:

    FileOutputStream fos = new FileOutputStream(file);
    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fos);
    
    int len;
    len = ois.read(BUFFER);
    int size = 0;
    while (true) {
        size += len;
        System.out.println(len + ": " + size);
        if (len == -1) {
            break;
        }
        bufferedOutputStream.write(BUFFER, 0, len);
        bufferedOutputStream.flush();
        len = ois.read(BUFFER);
    }
    

    to:

    try (final FileOutputStream fos = new FileOutputStream(file);
         final BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fos)) {
        final long expected = fileTransModel.getFileLength() == null? 0: fileTransModel.getFileLength();
        long size = 0;
        while (size < expected) {
            final int current = ois.read(BUFFER);
            bufferedOutputStream.write(BUFFER, 0, current);
            //bufferedOutputStream.flush(); //Flushing after each write would defeat the purpose of buffering, so better don't do it.
            size += current;
            System.out.println("So far: " + size);
        }
    } //Always close the file.
    

    Closing the client side ObjectOutputStream (the oos reference) right after writing the file contents, in order for example to get a -1 at the server side, is not an option here since closing oos would close the underlying OutputStream of the Socket, which in turn according to the documentation would close the Socket itself. So then if you were going to send the model back (ie from server to client), then a SocketException would be raised (the Socket would be no more usable).