开始文章前,还是要粘贴netty启动代码
/**
* @author 彭青松
*/
public class MyServer {
public static void main(String[] args) {
// 1.构建 bossGroup 和 workGroup
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
bossGroup.setIoRatio(20);
EventLoopGroup workGroup = new NioEventLoopGroup(1);
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 2.设置group
serverBootstrap.group(bossGroup, workGroup);
// 3.设置服务端option和attr
serverBootstrap.option(ChannelOption.SO_BACKLOG, 1);
serverBootstrap.attr(AttributeKey.valueOf("name"),"彭青松");
// 4.设置服务端的channel
serverBootstrap.channel(NioServerSocketChannel.class);
// 5.设置服务端的handler
serverBootstrap.handler(new MyServerHandler());
// 6.设置客户端的handler
serverBootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
// 7.客户端channel初始化的时候,调用这里
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new MyTcpClinetHandler());
}
});
try {
// 8.服务绑定端口,启动
ChannelFuture channelFuture = serverBootstrap.bind(8888).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
一.pipline概述
pipline是管道的意思,每一个channel建立后,内部都会创建一个pipline,pipline里面会把handler封装成一个双向链表,当channel有读写事件时候,数据就在这个pipline中传递,进入每一个handle,进行处理,如下如:
二.pipline初始化
pipline是在socketcChannel创建的时候一同创建出来的,我们可以看下NioSocketChannel和NioServerSocketChannel的集成体系
从图中可以看出,他俩都是集成AbstractNioChannel
在最后创建pipline
pipeline = newChannelPipeline();
protected DefaultChannelPipeline newChannelPipeline() {
return new DefaultChannelPipeline(this);
}
继续进入
protected DefaultChannelPipeline(Channel channel) {
//1.pipline中保存channel
this.channel = ObjectUtil.checkNotNull(channel, "channel");
succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise = new VoidChannelPromise(channel, true);
//2.创建尾节点
tail = new TailContext(this);
//3.创建头节点
head = new HeadContext(this);
//4.构建双向链表
head.next = tail;
tail.prev = head;
}
重点是创建头/尾的handlerContext
从集成图中可以看到这头尾handlercontext都是继承
AbstractChannelHandlerContext
下面我们分析
2.1 TailContext创建
创建的时候,会区分这个handlerContext是inboud还是outbound, 在消息从不同的入口进来时候,会触发inboud还是outboud的handler来处理数据
2.2 HeadContext创建
HeadContext和TailContext有共同的父类,他们创建的逻辑是一样,只是名字不同,HeadContext 是outbound事件
2.3 构建handlerContext双向链表
比较简单,就是头结点指向尾结点,尾结点指向头结点
三.添加handler
在文章开头的netty启动代码中,可以看到pipeline.addLast(new MyTcpClinetHandler()),这里就是添加handler,进入代码
而addLast0()方法也很简单,就是找到tail前面的一个节点,将需要添加的节点放在这里,并重新构建双向链表
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
四.删除handler
public final ChannelPipeline remove(ChannelHandler handler) {
remove(getContextOrDie(handler));
return this;
}
这里逻辑也比较简单,逻辑是
- 1.从head节点开始,遍历找到这个节点
- 2.删除该节点,并重构pipline的双向链表
- 3.触发handlerRemove()方法
五.inboound事件传播
客户端的读事件就是inboud事件,会从head节点一次往下找到类型是inboud的handlerContext,进行处理
当客户端的channel创建后,客户端写入数据后,服务端这个selector会监测到这个读事件
public final ChannelPipeline fireChannelRead(Object msg) {
// 这里是从head节点开始执行ChannelRead方法
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
接下来就是当前节点处理后, 就会继续找下一个inboud的handlerContext,进行触发,如果管道中这个消息没有被我们业务代码处理,最后会到tailHandlerContext中,这里只是打印日志,大家可以自行调试一下代码
六.outboud事件传播
如果服务端调用ctx.pipeline().write(msg)方法,就会从pipline的tail节点开始,依次触发write方法
ctx.pipeline().write(msg);
outbound和inboud的流程差不多,outboud是从tail节点开始,依次去查找outboud类型的handlerContext,然后触发write,走到HeadContext的时候,由unsafe类调用基层api,将消息发送出去
七.异常传播
异常传播,不区分inboud和outboud事件,直接往下传播,如果都没有被处理,会到tailContext中,打印日志