首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Netty: TCP客户端服务器文件传输:TooLongFrameException异常:

Netty: TCP客户端服务器文件传输:TooLongFrameException异常:
EN

Stack Overflow用户
提问于 2018-11-05 00:59:44
回答 1查看 1.5K关注 0票数 0

我是netty的新手,我正在尝试设计一个解决方案,如下所示,用于通过TCP从服务器向客户端传输文件:

代码语言:javascript
复制
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.

客户端-服务器文件传输以这种方式工作:

代码语言:javascript
复制
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

文件内容可以是任何内容(字符串、/image、/pdf等)和任何大小。

现在,我在服务器端得到这个TooLongFrameException:,尽管服务器只是解码从客户端接收到的路径,以便运行下面提到的代码(服务器/客户机)。

代码语言:javascript
复制
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帖子-- 所以Q1所以Q2所以Q3所以Q4。我了解了LengthFieldBasedDecoder,但不知道如何在服务器(编码端)配置相应的LengthFieldPrepender。根本不需要吗?

请给我指出正确的方向。

FileClient:

代码语言:javascript
复制
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:

代码语言:javascript
复制
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:

代码语言:javascript
复制
/**
 * 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:

代码语言:javascript
复制
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行动这里中的代码示例

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-11-06 13:00:15

您的服务器/客户端有多处问题。首先,对于客户端,您不需要为服务器初始化SslContext,而是执行如下操作:

代码语言:javascript
复制
sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();

在服务器端,您使用的SelfSignedCertificate本身并不是错误的,但希望提醒您,它只应用于调试目的,而不应用于生产。此外,您还使用了不建议使用的ChannelOption.SO_KEEPALIVE,因为保持活动间隔依赖于操作系统。此外,您还将Object En-/Decoder添加到管道中,在您的情况下,它不会做任何有用的事情,因此您可以删除它们。

此外,由于不完整和错误的参数列表,您配置了错误的LengthFieldBasedFrameDecoder。在netty博士中,您需要构造函数的版本,它定义了lengthFieldLengthinitialBytesToStrip。除了不剥离长度字段之外,您还定义了错误的lengthFieldLength,应该与您的LengthFieldPrependerlengthFieldLength相同,后者是4个字节。

代码语言:javascript
复制
new LengthFieldBasedFrameDecoder(64 * 1024, 0, 4, 0, 4)

在这两个处理程序中,您没有在en-/解码您的Charset时指定一个String,这可能会导致问题,因为如果没有定义字符集,那么系统默认将被使用,这可能会有所不同。你可以这样做:

代码语言:javascript
复制
//to encode the String
string.getBytes(StandardCharsets.UTF_8);

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

此外,如果没有将DefaultFileRegion添加到管道中,则尝试使用SslHandler,如果不添加LengthFieldHandler,这会很好,因为它们需要byte[]的内存副本才能发送到添加的length字段。此外,我建议使用ChunkedNioFile而不是ChunkedFile,因为它是非阻塞的,这总是一件好事。你会这样做的:

代码语言:javascript
复制
new ChunkedNioFile(randomAccessFile.getChannel())

关于如何解码ChunkedFile的最后一件事,因为它是分块的,您可以简单地用一个简单的OutputStream访问它们。下面是我的一个旧文件处理程序:

代码语言:javascript
复制
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();
    }
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/53147024

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档