前言
书上讲服务器客户端创建三个要点,线程模型(Group
),IO模型(NioSocketChannel
),处理逻辑。
这篇的Handler
和Pipeline
,就是我们IO
操作的处理逻辑。
然后下篇说ByteBuf
这个Netty
自己实现的数据封装组件。
Handler和Pipeline
我们主要谈论ChannelHandler
和ChannelPipeline
。
前文也说过这俩东西,Pipeline
就是多个Handler
构成的。
数据经过多个Handler
处理,不就是一个流水线加工的形式吗。
下面来具体说说。
首先,一个连接对应一个Channel
,这是前面说到过的。每个连接经过ServerSocketChannel
处理连接请求,生成一个与之对应的SocketChannel
。
而每个Channel
同样也对应一个Pipeline
。而一个Pipeline
可能里面有多个Handler
了。Pipeline
内部的Handler
是双向链表形式连接。
ChannelHandler
接口有两个子接口,ChannelInboundHandler
(实现类 ChannelInboundHandlerAdapter
)和ChannelOutboundHandler
(实现类 ChannelOutboundHandlerAdapter
)。
顾名思义,Inbound
自然是读数据时的处理,而Outbound
是写数据时的处理。
Inbound
的方法是channelRead()
,对应channel
读取数据时做的处理。
Outbound
的方法是write()
,对应向channel
写数据之前做什么处理。
所以ChannelInboundHandlerAdapter
内重写的channelRead()
会在读取Channel
数据时触发。
ChannelOutboundHandlerAdapter
内重写的write()
在写操作时触发。
Handler
构成的双向链表就可以看做Pipeline
那么这个双向链表是什么样的呢?
先看示例代码添加的Handler
new ServerBootstrap()
.group(new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println(1);
ctx.fireChannelRead(msg); // 1
}
});
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println(2);
ctx.fireChannelRead(msg); // 2
}
});
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println(3);
ctx.channel().write(msg); // 3
}
});
ch.pipeline().addLast(new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg,
ChannelPromise promise) {
System.out.println(4);
ctx.write(msg, promise); // 4
}
});
ch.pipeline().addLast(new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg,
ChannelPromise promise) {
System.out.println(5);
ctx.write(msg, promise); // 5
}
});
ch.pipeline().addLast(new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg,
ChannelPromise promise) {
System.out.println(6);
ctx.write(msg, promise); // 6
}
});
}
})
.bind(8080);
上述代码最后形成的双向链表是这样的。
这不就是按add
的顺序插进链表吗。确实是,但是执行操作的顺序是有变化的。
我们的读事件,会按链表中的Inbound
的顺序从前向后传递。每个Handler
执行自己的逻辑然后把事件传给下一个InboundHandler
。当然,不会传给OutboundHandler
。按图来说就是In_1
,In_2
,In_3
的顺序
而对于写事件在OutboundHandler中的传播顺序则与添加顺序相反,是从链表尾部最后add
的OutboundHandler
开始向前传到第一个OutboundHandler
。按图,执行顺序为,Out_6
,Out_5
,Out_4
。
即inboundHandler
的执行顺序与实际的添加顺序相同,而outboundHandler
则相反。
结语
本文仅涉及到Handler
处理的触发顺序。对于我们使用时一般在Handler
里做什么没有介绍。一般来说就是做数据的编码解码,或者打印一些信息。
总结一下,每个Channel
有自己的Pipeline
,内部的Handler
有我们进行配置。
其中读会走InboundHandler
处理。写会走OutBoundHandler
处理。
还有就是执行顺序,InboundHandler
的执行顺序与添加顺序相同。OutBoundHandler
则是相反。
下篇就是数据载体ByteBuf
了。
然后基本组件就说完了。Netty
的基本使用也就是这些组件的使用。
剩下的就是一些组件深层次的原理与现象了(比如主从Reactor
模型,组件的源码实现),这部分可能不影响使用,但对于理解Netty
很有帮助。
我觉得仅仅了解这些东西的基本使用,我们还是很难用它去做一个什么聊天室,RPC
之类的东西。
所以还是需要学习别人怎么做的,做这个的框架,要有什么样的功能怎么实现,会出现什么问题怎么解决。
感谢阅读,欢迎批评指正。
标签:netty,Pipeline,ctx,write,Handler,msg,new From: https://blog.csdn.net/qq_42939279/article/details/140698668