Могу ли я закрыть только fileoutputstream, но канал работает после отправки файла Netty? - PullRequest
0 голосов
/ 28 августа 2018

Несколько дней назад я боролся с тем, как получить доступ к файлу, отправленному NettyClient, не убивая NettyServer. Я получил решение по StackOverFlow и подробности вопроса здесь . Решение состоит в том, что клиент закрывает канал после отправки файла, а сервер закрывает fileoutputstream в методе channelInactive. Основной код ниже.

ClientHandler

public class FileClientHandler extends ChannelInboundHandlerAdapter {

private int readLength = 128;

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    sendFile(ctx.channel());
}

private void sendFile(Channel channel) throws IOException {
    File file = new File("C:\\Users\\xxx\\Desktop\\1.png");
    FileInputStream fis = new FileInputStream(file);
    BufferedInputStream bis = new BufferedInputStream(fis);

    ChannelFuture lastFuture = null;
    for (;;) {
        byte[] bytes = new byte[readLength];
        int readNum = bis.read(bytes, 0, readLength);
        if (readNum == -1) { // The end of the stream has been reached
            bis.close();
            fis.close();
            lastFuture = sendToServer(bytes, channel, 0);
            if(lastFuture == null) { // When our file is 0 bytes long, this is true
                channel.close();
            } else {
                lastFuture.addListener(ChannelFutureListener.CLOSE);
            }
            return;
        }
        lastFuture = sendToServer(bytes, channel, readNum);
    }
}

private ChannelFuture sendToServer(byte[] bytes, Channel channel, int length)
        throws IOException {
    return channel.writeAndFlush(Unpooled.copiedBuffer(bytes, 0, length));
}

}

ServerHandler

public class FileServerHandler extends ChannelInboundHandlerAdapter {

private File file = new File("C:\\Users\\xxx\\Desktop\\2.png");
private FileOutputStream fos;

public FileServerHandler() {
    try {
        if (!file.exists()) {
            file.createNewFile();
        } else {
            file.delete();
            file.createNewFile();
        }
        fos = new FileOutputStream(file);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

@Override
public void channelInactive(ChannelHandlerContext ctx) {
    System.out.println("I want to close fileoutputstream!");
    try {
        fos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
        throws Exception {
    ByteBuf buf = (ByteBuf) msg;
    try {
        buf.readBytes(fos, buf.readableBytes());
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        buf.release(); // Should always be done, even if writing to the file fails
    }
}

}

Если сейчас мне нужно отправить 10 тысяч картинок, но каждая картинка маленькая, как 1 КБ. Я должен закрыть и затем установить канал часто. Это вещь, которая тратит много ресурсов. Как я могу только закрыть fileoutputstream, но канал жив?

1 Ответ

0 голосов
/ 28 августа 2018

Это просто идея, и я не проверял ее, но вместо того, чтобы отправлять каждый файл в отдельном соединении, вы могли бы запустить поток, в который вы отправляете:

  1. Количество отправляемых файлов (один раз)
  2. Информация о файле и содержание (для каждого файла)
    1. Размер файла
    2. Размер имени файла
    3. Имя файла
    4. Содержимое файла (в байтах)

Клиент будет выглядеть примерно так:

public void sendFiles(Channel channel, File...files) {
    ByteBufAllocator allocator = PooledByteBufAllocator.DEFAULT;
    int fileCount = files.length;
    // Send the file count
    channel.write(allocator.buffer(4).writeInt(fileCount));
    // For each file
    Arrays.stream(files).forEach(f -> {         
        try {
            // Get the file content
            byte[] content = Files.readAllBytes(f.toPath());
            byte[] fileName = f.getAbsolutePath().getBytes(UTF8);
            // Write the content size, filename and the content
            channel.write(allocator.buffer(4 + content.length + fileName.length)
                    .writeInt(content.length)
                    .writeInt(fileName.length)
                    .writeBytes(fileName)
                    .writeBytes(content)
            );
        } catch (IOException e) {
            throw new RuntimeException(e); // perhaps do something better here.
        }           
    });
    // Flush the channel
    channel.flush();
}

На стороне сервера вам потребуется немного более сложный обработчик каналов. Я думал о воспроизводящем декодере. ( Пример здесь )

В этом примере декодер будет читать все файлы и затем переадресовывать их следующему обработчику, который получит список Upload экземпляров, но вы можете отправлять каждую загрузку по конвейеру после каждого полученного файла, так что Вы не выделяете столько памяти. Но цель состоит в том, чтобы отправить все ваши файлы в одном потоке, а не подключаться / отключаться для каждого файла.

...