本文是基於Netty4.1.x,Handler在Netty佔據着很重要的位置,跟Servlet中的filter很像,通過Handler可以完成消息的編解碼、攔截指定的消息、統一對日誌錯誤進行處理、統一對請求進行計數。所有的Handler都實現ChannelHandler接口,分爲兩大類,ChannelInboundHandler與ChannelOutboundHandler,ChannelInboundHandler對從客戶端發往服務器的報文進行處理,一般用來執行解碼、讀取客戶端數據、進行業務處理等;ChannelOutboundHandler對從服務器發往客戶端的報文進行處理,一般用來進行編碼、發送報文到客戶端。Netty中,可以註冊多個handler。ChannelInboundHandler按照註冊的先後順序執行;ChannelOutboundHandler按照註冊的先後順序逆序執行,如下圖所示,按照註冊的先後順序對Handler進行排序,request進入Netty後的執行順序爲:
從上圖可以看出,在入站時,request會依次經過相關的Inbound處理器,然後出站時response也經過相應的Outbound處理器。從上圖中可以看出Handler組成了一個鏈表放在ChannelPipeplie中,下面看看ChannelPipe中的Handler如何組成鏈表的。下面是一段NettyServer初始化的代碼:
public class NettyServer {
public static void main(String[] args) throws Exception {
new NettyServer().start("127.0.0.1", 8081);
}
public void start(String host, int port) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
EventLoopGroup bossGroup = new NioEventLoopGroup(0, executorService);//Boss I/O線程池,用於處理客戶端連接,連接建立之後交給work I/O處理
EventLoopGroup workerGroup = new NioEventLoopGroup(0, executorService);//Work I/O線程池
EventExecutorGroup businessGroup = new DefaultEventExecutorGroup(2);//業務線程池
ServerBootstrap server = new ServerBootstrap();//啓動類
server.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast(businessGroup, new ServerHandler());
}
});
server.childOption(ChannelOption.TCP_NODELAY, true);
server.childOption(ChannelOption.SO_RCVBUF, 32 * 1024);
server.childOption(ChannelOption.SO_SNDBUF, 32 * 1024);
InetSocketAddress addr = new InetSocketAddress(host, port);
server.bind(addr).sync().channel();//啓動服務
}
}
從上面看出在initChannel時會調用 pipeline.addLast("decoder", new StringDecoder());逐個將Handler加入到Pipeline,下在看看 pipeline.addLast()這個方法:
@Override
public final ChannelPipeline addLast(String name, ChannelHandler handler) {
return addLast(null, name, handler);
}
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
checkMultiplicity(handler);
newCtx = newContext(group, filterName(name, handler), handler);
addLast0(newCtx);
// If the registered is false it means that the channel was not registered on an eventLoop yet.
// In this case we add the context to the pipeline and add a task that will call
// ChannelHandler.handlerAdded(...) once the channel is registered.
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
callHandlerAddedInEventLoop(newCtx, executor);
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
上面代碼片中 addLast()方法中先調用 checkMultiplicity(handler)檢查一下是否有重複,然後將handler包裝成一個AbstractChannelHandlerContext然後再調用 addLast0(newCtx);將newCtx放入到鏈表中,從addLast0方法中可以看出,處理鏈是一個雙向鏈表。到這裏處理鏈的執行邏輯及組成方式就說完了總結一下:
- 處理鏈是一個有順的雙向鏈表放在ChannelPipeline(DefaultChannelPipeline)中,消息上行或下行時依次會被相關的處理器處理;
- 可以通過 channel.pipeline().addLast()、 channel.pipeline().addFirst()、channel.pipeline().addBefore()等方法向鏈表的指定位置添加處理器,也可以在處理過程中動態添加或刪除處理器;
- 可以爲Handler指定處理的業務線程池;
- 可以使用相關的api將消息在處理鏈路中流轉,如ctx.fireChannelRead()表示將消息傳遞到下一個處理器,ctx.channel().pipeline()表示將消息放到pipeline讓處理器從第一個處理器開始處理;