首页 > 解决方案 > ReadComplete() 调用了两次

问题描述

注意:这个问题是过去发布的,但今天使用 ChannelOption.MAX_MESSAGES_PER_READ 的解决方案不起作用。此外,该选项已弃用。

注意:作为提醒..我是 Netty 的新手。

使用 Netty 4.1.51,当我使用 'curl -v http://localhost:8080/' 刺激以下代码时,我的程序的控制台输出显示:

Server listening on 8080
Read
GET / HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.71.1
Accept: */*

ReadComplete
Write Hello
flush
ReadComplete
Write Hello
flush

处理程序 read() 方法被调用一次, readComplete() 方法被调用两次,然而,curl 似乎对这种交换很满意。

curl -v http://localhost:8080/
*   Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.71.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 ok
< Content-Type: text/plain
< Content-Length: 5
<
Hello* Connection #0 to host localhost left intact

我似乎无法理解发生了什么..最近有人遇到过这个问题吗?

readComplete() 不应该在一次或多次读取后发生一次吗?

感谢大家有空来看这个。

-jmr


private static void bootstrap1() {

    int port = 8080;
    EventLoopGroup group = new NioEventLoopGroup();
    ServerBootstrap bootstrap = new ServerBootstrap();

    bootstrap
        .group(group)
        .channel(NioServerSocketChannel.class)
        .childHandler(new ChannelInitializer<SocketChannel>()
        {
            @Override protected void initChannel(SocketChannel ch) 
            throws Exception 
            {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast("out", new OutHandler());
                pipeline.addLast("in", new InHandler());
            }
        });
    
    ChannelFuture future = bootstrap.bind(new InetSocketAddress(port));
    future.addListener((ChannelFutureListener) (ChannelFuture future1) -> 
    {
        if (future1.isSuccess()) {
            pr("Server listening on " + port);
        } else {
            future1.cause().printStackTrace();
        }
    });        
}

private static class InHandler extends ChannelInboundHandlerAdapter {
    
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        pr("Read");
        if (msg instanceof ByteBuf) {
            ByteBuf buf = (ByteBuf) msg;
            pr("" + buf.readCharSequence(buf.readableBytes(), Charset.defaultCharset()));
        }
        ctx.fireChannelRead(msg);
    }
    
    @Override public void channelReadComplete(ChannelHandlerContext ctx) 
            throws Exception 
    {
        pr("ReadComplete");
        ByteBuf buf = Unpooled.buffer();
        buf.writeCharSequence("Hello", Charset.defaultCharset());
        ctx.writeAndFlush(buf);
        ctx.fireChannelReadComplete();
    }
}

private static class OutHandler extends ChannelOutboundHandlerAdapter {
    
    @Override public void write(ChannelHandlerContext ctx, Object msg, 
            ChannelPromise promise) 
            throws Exception 
    {
        ByteBuf in = (ByteBuf) msg;
        pr("Write " + in.getCharSequence(0, in.readableBytes(), Charset.defaultCharset()));
        
        ByteBuf buf = Unpooled.buffer();
        buf.writeCharSequence("HTTP/1.1 200 ok\r\n", Charset.defaultCharset());
        buf.writeCharSequence("Content-Type: text/plain\r\n", Charset.defaultCharset());
        buf.writeCharSequence("Content-Length: 5\r\n\r\n", Charset.defaultCharset());
        buf.writeCharSequence(in.readCharSequence(5, Charset.defaultCharset()), Charset.defaultCharset());
        in.release();
        ctx.write(buf);
    }
    
    @Override public void flush(ChannelHandlerContext ctx) 
            throws Exception 
    {
        pr("flush");
        ctx.flush();
    }
}

标签: netty

解决方案


除了channelReadComplete被调用两次之外,您还编写了两次 HTTP 响应,由重复的“Write Hello”日志消息指示。

channelReadComplete被调用两次,因为自动读取已打开(默认情况下发生)。要关闭它并触发初始只读,您可以修改ChannelInitializer如下:

.childHandler(new ChannelInitializer<SocketChannel>()
{
    @Override protected void initChannel(SocketChannel ch)
        throws Exception
    { 
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast("out", new OutHandler());
        pipeline.addLast("in", new InHandler());
        ch.config().setAutoRead(false);
        ch.read();
    }
});

说了这么多,channelReadComplete实际上并不是创建和发送响应的正确位置。在您的情况下,这样做会导致响应的双重发送。curl 可能没问题,但这不是你想要的。channelRead一旦您收集了完整的请求,该方法是发送响应的更好地方。

对于参考回声服务器,请看这里:https ://github.com/normanmaurer/netty-in-action/tree/2.0-SNAPSHOT/chapter2/Server/src/main/java/nia/chapter2/echoserver

我强烈推荐 NIA 的书,让你在所有事情上都指向正确的方向。


推荐阅读