-->

Incomplete File Copy Java NIO

2019-09-12 17:58发布

问题:

I'm having problem with reading my file. Im quite new to NIO too. The actual size of the file I want to send to the server is almost 900MB and only received 3MB.

The server's side code for reading:

private void read(SelectionKey key) throws IOException{
    SocketChannel socket = (SocketChannel)key.channel();
    RandomAccessFile aFile = null;
    ByteBuffer buffer = ByteBuffer.allocate(300000000);
    try{
        aFile = new RandomAccessFile("D:/test2/test.rar","rw");

        FileChannel inChannel = aFile.getChannel();

        while(socket.read(buffer) > 0){
            buffer.flip();
            inChannel.write(buffer);
            buffer.compact();
        }
        System.out.println("End of file reached..");
    }catch(Exception e){
        e.printStackTrace();
    }
}

This is my code for the write method of the client side:

private void write(SelectionKey key) throws IOException {
    SocketChannel socket = (SocketChannel) key.channel();
    RandomAccessFile aFile = null;
    try {
        File f = new File("D:/test.rar");
        aFile = new RandomAccessFile(f, "r");
        ByteBuffer buffer = ByteBuffer.allocate(300000000);

        FileChannel inChannel = aFile.getChannel();
        while (inChannel.read(buffer) > 0) {
            buffer.flip();
            socket.write(buffer);
            buffer.compact();
        }
        aFile.close();
        inChannel.close();

        key.interestOps(SelectionKey.OP_READ);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

回答1:

  1. You're opening a new file every time the socket channel becomes readable. Every TCP segment that arrives, you're recreating the target file, and therefore throwing away whatever was received before.

    The simple fix to that would be to open the file for append on every OP_READ, but it would remain ridicously inefficient. You should open the target file as soon as you know what it is, and close it when you read end of stream from the sender, or when you've read the entire contents if that isn't signalled by end of stream. You haven't disclosed your application protocol so I can't be more specific.

  2. read() returns zero when there is no data available to be read without blocking. You're treating that as an end of file. It isn't.
  3. The canonical way to write between channels is as follows:

    while ((in.read(buffer) > 0 || buffer.position() > 0)
    {
        buffer.flip();
        out.write(buffer);
        buffer.compact();
    }
    

    However if the target is a non-blocking socket channel this gets considerably more complex: you have to manipulate whether you're selected for OP_WRITE or not depending on whether or not the last write() returned zero. You will find this explained in a large number of posts here, many by me.

  4. I have never seen any cogent reason for non-blocking I/O in the client side, unless it connects to multiple servers (a web crawler for example). I would use blocking mode or java.net.Socket at the client, which will obviate the write complexity referred to above.

NB you don't need to close both the RandomAccessFile and the FileChannel that was derived from it. Either will do.