關於Netty的一些理解、實踐與陷阱

Mooooon發表於2017-12-14

核心概念的理解

Netty對於網路層進行了自己的抽象,用Channel表示連線,讀寫就是Channel上發生的事件,ChannelHandler用來處理這些事件,ChannelPipeline基於unix哲學提供了一種優雅的組織ChannelHandler的方式,用管道解耦不同層面的處理。現在回過頭來看看,真的是非常天才和優雅的設計,是我心中API設計的典範之一了。

TCP半包、粘包

使用Netty內建的LineBasedFrameDecoder或者LengthFieldBasedFrameDecoder,我們只要在pipeline中新增,就解決了這個問題。

Writtable問題

有時候,由於TCP的send buffer滿了,向channel的寫入會失敗。我們需要檢查channel().isWritable()標記來確定是否執行寫入。

處理耗時任務

Netty In Action以及網上的一些資料中,都沒有很直接的展示如何在Netty中去處理耗時任務。其實也很簡單,只要給handler指定一個事件迴圈就可以,例如

public class MyChannelInitializer extends ChannelInitializer<Channel> {
    private static EventExecutorGroup longTaskGroup = new DefaultEventExecutorGroup(5);

    protected void initChannel(Channel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();
        ...
        pipeline.addLast(longTaskGroup, new PrintHandler());

    }
}
複製程式碼

Pitfall

Netty的ChannelPipeline只有一條雙向鏈,訊息入站,經過一串InBoundHandler之後,以相反的順序再經過OutBoundHandler出站.因此,我們自定義的handler一般會處於pipeline的末尾!

舉個例子,當以如下順序新增handler時,如果呼叫ChannelHandlerContext上的writeAndFlush方法,出站訊息是無法經過StringEncoder的

public class MyChannelInitializer extends ChannelInitializer<Channel> {
    private static EventExecutorGroup longTaskGroup = new DefaultEventExecutorGroup(5);

    protected void initChannel(Channel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();
        pipeline.addLast(new LineBasedFrameDecoder(64 * 1024));
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(longTaskGroup, new PrintHandler());
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
    }
}

複製程式碼

這個問題有兩個解決方式

  1. 調整handler的順序
  2. 呼叫channel上的writeAndFlush方法,強制使訊息在整個pipeline上流動

調整handler的順序

public class MyChannelInitializer extends ChannelInitializer<Channel> {
    private static EventExecutorGroup longTaskGroup = new DefaultEventExecutorGroup(5);

    protected void initChannel(Channel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();
        pipeline.addLast(new LineBasedFrameDecoder(64 * 1024));
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast(longTaskGroup, new PrintHandler());
    }
}
複製程式碼

呼叫Channel上的writeAndFlush方法

public class PrintHandler extends SimpleChannelInboundHandler<String> {
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
//        ctx.writeAndFlush(msg);
        ctx.channel().writeAndFlush(msg);
        System.out.println(msg);
    }
}
複製程式碼

參考

http://www.voidcn.com/article/p-yhpuvvkx-mm.html https://stackoverflow.com/questions/37474482/dealing-with-long-time-task-such-as-sql-query-in-netty 《Netty In Action》

相關文章