Нетти: Клиентский сервер TCP Передача файлов: Исключение TooLongFrameException: - PullRequest
0 голосов
/ 05 ноября 2018

Я новичок в Netty и пытаюсь разработать решение для передачи файла с сервера на клиент по протоколу TCP:

1. Zero copy based file transfer in case of non-ssl based transfer (Using default region of the file)
2. ChunkedFile transfer in case of SSL based transfer.

Передача файлов клиент-сервер работает следующим образом:

1. The client sends the location of the file to be transfered
2. Based on the location (sent by the client) the server transfers the file to the client

Содержимое файла может быть любым (String / image / pdf и т. Д.) И любого размера.

Теперь я получаю TooLongFrameException: на стороне сервера, хотя сервер просто декодирует путь, полученный от клиента, для запуска кода, указанного ниже (сервер / клиент).

io.netty.handler.codec.TooLongFrameException: Adjusted frame length exceeds 65536: 215542494061 - discarded
    at io.netty.handler.codec.LengthFieldBasedFrameDecoder.fail(LengthFieldBasedFrameDecoder.java:522)
    at io.netty.handler.codec.LengthFieldBasedFrameDecoder.failIfNecessary(LengthFieldBasedFrameDecoder.java:500)

Теперь Мой вопрос:

  1. Я ошибаюсь с порядком кодеров и декодеров и его конфигурацией? Если это так, как правильно настроить его для получения файла с сервера?
  2. Я просмотрел несколько связанных сообщений StackOverflow SO Q1 , SO Q2 , SO Q3 , SO Q4 . Я узнал о LengthFieldBasedDecoder, но не узнал, как настроить соответствующий ему объект LengthFieldPrepender на сервере (сторона кодирования). Это вообще нужно?

Пожалуйста, укажите мне правильное направление.

FileClient:

public final class FileClient {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port", SSL ? "8992" : "8023"));
    static final String HOST = System.getProperty("host", "127.0.0.1");

    public static void main(String[] args) throws Exception {
        // Configure SSL.
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }

        // Configure the client
        EventLoopGroup group = new NioEventLoopGroup();

        try {

            Bootstrap b = new Bootstrap();
            b.group(group)
            .channel(NioSocketChannel.class)
            .option(ChannelOption.SO_KEEPALIVE, true)
            .handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    if (sslCtx != null) {
                        pipeline.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
                    }
                    pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(64*1024, 0, 8));
                    pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
                    pipeline.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null)));
                    pipeline.addLast(new ObjectEncoder());
                    pipeline.addLast( new FileClientHandler());                }
             });


            // Start the server.
            ChannelFuture f = b.connect(HOST,PORT).sync();

            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down all event loops to terminate all threads.
            group.shutdownGracefully();
        }
    }
}

FileClientHandler:

public class FileClientHandler extends ChannelInboundHandlerAdapter{

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        String filePath = "/Users/Home/Documents/Data.pdf";
        ctx.writeAndFlush(Unpooled.wrappedBuffer(filePath.getBytes()));
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println("File Client Handler Read method...");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();

    }
}

FileServer:

/**
 * Server that accept the path of a file and echo back its content.
 */
public final class FileServer {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port", SSL ? "8992" : "8023"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }

        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_KEEPALIVE, true).handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            if (sslCtx != null) {
                                pipeline.addLast(sslCtx.newHandler(ch.alloc()));
                            }
                            pipeline.addLast("frameDecoder",new LengthFieldBasedFrameDecoder(64*1024, 0, 8));
                            pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
                            pipeline.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null)));
                            pipeline.addLast(new ObjectEncoder());

                            pipeline.addLast(new ChunkedWriteHandler());
                            pipeline.addLast(new FileServerHandler());
                        }
                    });

            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();

            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

FileServerHandler:

public class FileServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object obj) throws Exception {
        RandomAccessFile raf = null;
        long length = -1;
        try {
            ByteBuf buff = (ByteBuf)obj;

            byte[] bytes = new byte[buff.readableBytes()];
            buff.readBytes(bytes);

            String msg = new String(bytes);

            raf = new RandomAccessFile(msg, "r");
            length = raf.length();
        } catch (Exception e) {
            ctx.writeAndFlush("ERR: " + e.getClass().getSimpleName() + ": " + e.getMessage() + '\n');
            return;
        } finally {
            if (length < 0 && raf != null) {
                raf.close();
            }
        }

        if (ctx.pipeline().get(SslHandler.class) == null) {
            // SSL not enabled - can use zero-copy file transfer.
            ctx.writeAndFlush(new DefaultFileRegion(raf.getChannel(), 0, length));
        } else {
            // SSL enabled - cannot use zero-copy file transfer.
            ctx.writeAndFlush(new ChunkedFile(raf));
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        System.out.println("Exception server.....");
    }
}

Я упомянул Netty In Action и примеры кода здесь

1 Ответ

0 голосов
/ 06 ноября 2018

С вашим сервером / клиентом не так много вещей. Прежде всего SSL, для клиента вам не нужно инициализировать SslContext для сервера, вместо этого вы должны сделать что-то вроде этого:

sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();

На стороне сервера вы используете SelfSignedCertificate, что само по себе не является ошибкой, но хотелось бы напомнить вам, что его следует использовать только для целей отладки, а не в производстве. Кроме того, вы используете ChannelOption.SO_KEEPALIVE, что не рекомендуется, поскольку интервал поддержки активности зависит от ОС. Кроме того, вы добавили Object En-/Decoder в свой конвейер, который в вашем случае не делает ничего полезного, поэтому вы можете удалить их.

Также вы неправильно настроили LengthFieldBasedFrameDecoder из-за неполного и неправильного параметра list . В netty docs вам нужна версия конструктора, которая определяет lengthFieldLength и initialBytesToStrip. Помимо отсутствия поля длины, вы также определили неверный lengthFieldLength, который должен совпадать с вашим LengthFieldPrepender lengthFieldLength, который составляет 4 байта. В заключение вы можете использовать конструктор так:

new LengthFieldBasedFrameDecoder(64 * 1024, 0, 4, 0, 4)

В обоих ваших обработчиках вы не указываете Charset при кодировании / декодировании вашего String, что может привести к проблемам, потому что, если не определен «Charset», будет использоваться системное значение по умолчанию, которое может варьироваться. Вы можете сделать что-то вроде этого:

//to encode the String
string.getBytes(StandardCharsets.UTF_8);

//to decode the String
new String(bytes, StandardCharsets.UTF_8);

Кроме того, вы попытались использовать DefaultFileRegion, если в конвейер не было добавлено SslHandler, что было бы хорошо, если бы вы не добавили LengthFieldHandler, поскольку им потребуется копия байта [] в памяти для отправить в поле добавленной длины. Более того, я бы рекомендовал использовать ChunkedNioFile вместо ChunkedFile, потому что он неблокирует, что всегда хорошо. Вы бы сделали это так:

new ChunkedNioFile(randomAccessFile.getChannel())

И еще одна заключительная вещь о том, как декодировать a ChunkedFile, так как он разбит на куски, вы можете просто объединить их вместе с помощью простого OutputStream. Вот мой старый обработчик файлов:

public class FileTransferHandler extends SimpleChannelInboundHandler<ByteBuf> {

    private final Path path;
    private final int size;
    private final int hash;

    private OutputStream outputStream;
    private int writtenBytes = 0;
    private byte[] buffer = new byte[0];

    protected FileTransferHandler(Path path, int size, int hash) {
        this.path = path;
        this.size = size;
        this.hash = hash;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
        if(this.outputStream == null) {
            Files.createDirectories(this.path.getParent());
            if(Files.exists(this.path))
                Files.delete(this.path);
            this.outputStream = Files.newOutputStream(this.path, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
        }

        int size = byteBuf.readableBytes();
        if(size > this.buffer.length)
            this.buffer = new byte[size];
        byteBuf.readBytes(this.buffer, 0, size);

        this.outputStream.write(this.buffer, 0, size);
        this.writtenBytes += size;

        if(this.writtenBytes == this.size && MurMur3.hash(this.path) != this.hash) {
            System.err.println("Received file has wrong hash");
            return;
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        if(this.outputStream != null)
            this.outputStream.close();
    }
}
...