- 浏览: 10303 次
- 性别:
- 来自: 成都
最新评论
netty中的管道--ChannelPipeline机制
前面我们在提到ChannelPipeline的地方只是简单的描述了一下,这里我们再进一步深入到ChannelPipeline的内部中。
在netty中每一个channel都会只有一个ChannelPipeline与之对应.
在看看AbstractChannel类的构造函数吧,channel的初始化都会到这个父类的构造函数中。
DefaultChannelPipeline的构造函数:
保存与之关联的channel到DefaultChannelPipeline对象中。创建了tail和head字段,这个是双向链表的头和尾,在DefaultChannelPipeline维护了一个以AbstractChannelHandlerContext为节点的双向链表。TailContext继承了AbstractChannelHandlerContext实现了ChannelInboundHandler。
HeadContext继承了AbstractChannelHandlerContext实现了ChannelOutboundHandler和ChannelInboundHandler。
看看HeadContext的构造函数:
HeadContext调用了父类AbstractChannelHandlerContext的构造器,并传入参数inbound=false,outbound= true。TailContext的构造器与HeadContext的相反。传入参数inbound=true,outbound=false。
HeadContext是一个outboundHandler,TailContext是一个inboundHandler。HeadContext和TailContext即是一个ChannelHandler,又是一个ChannelHandlerContext。
以上就是一个通道的DefaultChannelPipeline自身的组成参数。那么我们在使用管道的时候,都会向里面添加自定义的ChannelHandler。这样才会发挥出DefaultChannelPipeline的魅力。
在EchoClient中:
在调用.handler方法时候传入了ChannelInitializer对象,它最终实现了ChannelHandler接口,那么在ChannelInitializer对象是怎么添加到DefaultChannelPipeline的?
handler方法会调用到AbstractBootstrap实例的handler方法:
这个方法做了一件事件就是把传入的handler实例赋值给AbstractBootstrap的handler变量。
还记得我们在第三节讲到的initAndRegister方法不。在此方法中调用了channel = channelFactory().newChannel()方法创建了一个新的channel后,就会调用init方法:
这个方法在Bootstrap类中会重写:
handler()获取我们在刚刚赋值的handler变量的值(ChannelInitializer),然后添加到管道的最后:调用的addLast方法。看看DefaultChannelPipeline的addLast方法:
newContext(group, filterName(name, handler), handler);为这个handler创建一个与之关联的DefaultChannelHandlerContext实例。在DefaultChannelHandlerContext中有一个handler变量保存传入关联的handler实例(ChannelInitializer)。
看看DefaultChannelHandlerContext的构造函数:
看2个方法:
这两个方法就是判断了Handler是不是实现了ChannelOutboundHandler或者是ChannelInboundHandler接口。这个2个方法返回的true或者是false都会传入到父类中:AbstractChannelHandlerContext。
ChannelInitializer实现了ChannelInboundHandler接口,因此这里实例化的 DefaultChannelHandlerContext的inbound = true,outbound = false。这两个字段关系到在pipeline中的事件的流向与分类。
在创建好DefaultChannelHandlerContext后(在pipeline的链表中只会加入ChannelHandlerContext对象),会将他插入到pipeline的双向链表中。
这个是典型的双向链表的插入操作。
还记得channel的注册过程不,在第三节的时候,我们讨论的channel的注册过程,在此过程中调用了AbstractUnsafe.register0方法中调用了pipeline.fireChannelRegistered() :
AbstractChannelHandlerContext中的invokeChannelRegistered:
next变量的实例就是HeadContext,invokeChannelRegistered调用的AbstractChannelHandlerContext的:
这里handler()调用的是DefaultChannelHandlerContext中的handler()方法。返回的就是ChannelInitializer对象。这就是和前面关联起来了。
initChannel方法:
initChannel((C) ctx.channel());熟悉吧,这个就会调用我们在EchoClient类中覆盖的方法:
再调用remove(ctx)后,pipeline里面就是head---->EchoClientHandler---->tail。
前面我们在提到ChannelPipeline的地方只是简单的描述了一下,这里我们再进一步深入到ChannelPipeline的内部中。
在netty中每一个channel都会只有一个ChannelPipeline与之对应.
在看看AbstractChannel类的构造函数吧,channel的初始化都会到这个父类的构造函数中。
protected AbstractChannel(Channel parent) { this.parent = parent; unsafe = newUnsafe(); pipeline = newChannelPipeline(); } protected DefaultChannelPipeline newChannelPipeline() { return new DefaultChannelPipeline(this); }
DefaultChannelPipeline的构造函数:
protected DefaultChannelPipeline(Channel channel) { this.channel = ObjectUtil.checkNotNull(channel, "channel"); tail = new TailContext(this); head = new HeadContext(this); head.next = tail; tail.prev = head; }
保存与之关联的channel到DefaultChannelPipeline对象中。创建了tail和head字段,这个是双向链表的头和尾,在DefaultChannelPipeline维护了一个以AbstractChannelHandlerContext为节点的双向链表。TailContext继承了AbstractChannelHandlerContext实现了ChannelInboundHandler。
HeadContext继承了AbstractChannelHandlerContext实现了ChannelOutboundHandler和ChannelInboundHandler。
看看HeadContext的构造函数:
HeadContext(DefaultChannelPipeline pipeline) { super(pipeline, null, HEAD_NAME, false, true); unsafe = pipeline.channel().unsafe(); setAddComplete(); }
HeadContext调用了父类AbstractChannelHandlerContext的构造器,并传入参数inbound=false,outbound= true。TailContext的构造器与HeadContext的相反。传入参数inbound=true,outbound=false。
HeadContext是一个outboundHandler,TailContext是一个inboundHandler。HeadContext和TailContext即是一个ChannelHandler,又是一个ChannelHandlerContext。
以上就是一个通道的DefaultChannelPipeline自身的组成参数。那么我们在使用管道的时候,都会向里面添加自定义的ChannelHandler。这样才会发挥出DefaultChannelPipeline的魅力。
在EchoClient中:
EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); if (sslCtx != null) { p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT)); } p.addLast(new EchoClientHandler()); } }); ChannelFuture f = b.connect(HOST, PORT).sync(); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); }
在调用.handler方法时候传入了ChannelInitializer对象,它最终实现了ChannelHandler接口,那么在ChannelInitializer对象是怎么添加到DefaultChannelPipeline的?
handler方法会调用到AbstractBootstrap实例的handler方法:
public B handler(ChannelHandler handler) { if (handler == null) { throw new NullPointerException("handler"); } this.handler = handler; return (B) this; }
这个方法做了一件事件就是把传入的handler实例赋值给AbstractBootstrap的handler变量。
还记得我们在第三节讲到的initAndRegister方法不。在此方法中调用了channel = channelFactory().newChannel()方法创建了一个新的channel后,就会调用init方法:
这个方法在Bootstrap类中会重写:
@Override @SuppressWarnings("unchecked") void init(Channel channel) throws Exception { ChannelPipeline p = channel.pipeline(); p.addLast(handler()); final Map<ChannelOption<?>, Object> options = options(); synchronized (options) { setChannelOptions(channel, options, logger); } final Map<AttributeKey<?>, Object> attrs = attrs(); synchronized (attrs) { for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) { channel.attr((AttributeKey<Object>) e.getKey()).set(e.getValue()); } } }
handler()获取我们在刚刚赋值的handler变量的值(ChannelInitializer),然后添加到管道的最后:调用的addLast方法。看看DefaultChannelPipeline的addLast方法:
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 (!registered) { newCtx.setAddPending(); callHandlerCallbackLater(newCtx, true); return this; } EventExecutor executor = newCtx.executor(); if (!executor.inEventLoop()) { newCtx.setAddPending(); executor.execute(new Runnable() { @Override public void run() { callHandlerAdded0(newCtx); } }); return this; } } callHandlerAdded0(newCtx); return this; }
newContext(group, filterName(name, handler), handler);为这个handler创建一个与之关联的DefaultChannelHandlerContext实例。在DefaultChannelHandlerContext中有一个handler变量保存传入关联的handler实例(ChannelInitializer)。
看看DefaultChannelHandlerContext的构造函数:
DefaultChannelHandlerContext( DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) { super(pipeline, executor, name, isInbound(handler), isOutbound(handler)); if (handler == null) { throw new NullPointerException("handler"); } this.handler = handler; }
看2个方法:
private static boolean isInbound(ChannelHandler handler) { return handler instanceof ChannelInboundHandler; } private static boolean isOutbound(ChannelHandler handler) { return handler instanceof ChannelOutboundHandler; }
这两个方法就是判断了Handler是不是实现了ChannelOutboundHandler或者是ChannelInboundHandler接口。这个2个方法返回的true或者是false都会传入到父类中:AbstractChannelHandlerContext。
ChannelInitializer实现了ChannelInboundHandler接口,因此这里实例化的 DefaultChannelHandlerContext的inbound = true,outbound = false。这两个字段关系到在pipeline中的事件的流向与分类。
在创建好DefaultChannelHandlerContext后(在pipeline的链表中只会加入ChannelHandlerContext对象),会将他插入到pipeline的双向链表中。
private void addLast0(AbstractChannelHandlerContext newCtx) { AbstractChannelHandlerContext prev = tail.prev; newCtx.prev = prev; newCtx.next = tail; prev.next = newCtx; tail.prev = newCtx; }
这个是典型的双向链表的插入操作。
还记得channel的注册过程不,在第三节的时候,我们讨论的channel的注册过程,在此过程中调用了AbstractUnsafe.register0方法中调用了pipeline.fireChannelRegistered() :
@Override public final ChannelPipeline fireChannelRegistered() { AbstractChannelHandlerContext.invokeChannelRegistered(head); return this; }
AbstractChannelHandlerContext中的invokeChannelRegistered:
static void invokeChannelRegistered(final AbstractChannelHandlerContext next) { EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeChannelRegistered(); } else { executor.execute(new Runnable() { @Override public void run() { next.invokeChannelRegistered(); } }); } }
next变量的实例就是HeadContext,invokeChannelRegistered调用的AbstractChannelHandlerContext的:
private void invokeChannelRegistered() { if (invokeHandler()) { try { ((ChannelInboundHandler) handler()).channelRegistered(this); } catch (Throwable t) { notifyHandlerException(t); } } else { fireChannelRegistered(); } }
这里handler()调用的是DefaultChannelHandlerContext中的handler()方法。返回的就是ChannelInitializer对象。这就是和前面关联起来了。
public final void channelRegistered(ChannelHandlerContext ctx) throws Exception { if (initChannel(ctx)) { ctx.pipeline().fireChannelRegistered(); } else { ctx.fireChannelRegistered(); } }
initChannel方法:
private boolean initChannel(ChannelHandlerContext ctx) throws Exception { if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard against re-entrance. try { initChannel((C) ctx.channel()); } catch (Throwable cause) { exceptionCaught(ctx, cause); } finally { remove(ctx); } return true; } return false; }
initChannel((C) ctx.channel());熟悉吧,这个就会调用我们在EchoClient类中覆盖的方法:
.handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); if (sslCtx != null) { p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT)); } p.addLast(new EchoClientHandler()); } });
再调用remove(ctx)后,pipeline里面就是head---->EchoClientHandler---->tail。
发表评论
-
netty探索之旅八
2017-03-09 13:27 361我们继续EventLoop。走起! 在前一节我们谈到了一个e ... -
netty探索之旅七
2017-03-06 16:28 361前面我们分析了Pipeline,还有一个东西值得我们去研究研究 ... -
netty探索之旅六
2017-03-05 15:45 395netty中的管道--ChannelPipeline的事件传输 ... -
netty探索之旅三
2017-02-17 16:00 496下面就开始我们的探索 ... -
netty探索之旅二
2017-02-13 13:30 315上一篇只是简单的介绍了一下NIO中的Selector。 这里我 ... -
netty探索之旅四
2017-02-27 16:19 356上一篇我们研究了netty的客户端代码,这一篇我们研究一下服务 ... -
netty探索之旅一
2017-02-12 16:38 335其实一直都在关注NETTY,前面也花了点时间去看过,但是 ...
相关推荐
Java进阶技术-netty进阶之路
《Netty进阶之路-跟着案例学Netty》是由知名技术专家李林峰撰写的一本专为Java开发者深入理解Netty框架而准备的书籍。这本书旨在通过实例教学,帮助读者全面掌握Netty的核心特性和实战技巧,提升网络编程的能力。 ...
Netty进阶之路 跟着案例学Netty 整本书无密码,Netty进阶之路 跟着案例学Netty
《Netty进阶之路:跟着案例学Netty》中的案例涵盖了Netty的启动和停止、内存、并发多线程、性能、可靠性、安全等方面,囊括了Netty绝大多数常用的功能及容易让人犯错的地方。在案例的分析过程中,还穿插讲解了Netty...
ChannelHandlerAdapter 4.X版本和5.X版本的差别很大。ChannelRead是属于5.X版本的4.X版本没有这个方法,所以如果要用ChannelRead。可以更换5.X版本的Netty。
精选自1000多个一线业务实际案例,从原理到实践全景式讲解Netty项目实践,快速领悟Netty专家花大量时间积累的经验,提高编程水平及分析解决问题的能力,《Netty木又威指南》作者力作,众专家力荐 Netty将Java NIO...
5. **NioServerSocketChannel**:在Netty中,ServerChannel的具体实现之一是`NioServerSocketChannel`,它使用Java NIO(非阻塞I/O)来处理连接。NioServerSocketChannel监听指定的端口,并在有新的连接请求时创建新...
读书笔记:Netty权威指南学习之旅
在本文中,我们将深入探讨 Netty 5 版本中的关键概念和特性,以及如何创建一个简单的客户端-服务器实例。 Netty 的核心设计理念是基于 NIO(非阻塞 I/O)模型,这使得它在处理大量并发连接时表现优秀。它提供了丰富...
Netty 是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。在本文中,我们将深入探讨Netty的基本概念,通过“Hello World”范例来理解其工作原理。 首先,让我们理解...
《NIO+Netty5视频教程与Netty源码剖析视频教程》是一份全面解析网络编程框架Netty的教育资源,旨在帮助学习者深入理解和应用NIO(非阻塞I/O)以及Netty5的核心技术。该教程分为两个主要部分,分别针对理论分析和实战...
这个小程序使用netty5进行udp网络通讯,客户端有两种,1:用netty5类库发送DatagramPacket和接收 2:直接使用DatagramSocket发送接收DatagramPacket 先运行netty_server的QuoteOfTheMomentServer, 在运行netty_...
在《Netty进阶之路:跟着案例学Netty》中,作者将在过去几年实践中遇到的问题,以及Netty学习者咨询的相关问题,进行了归纳和总结,以问题案例做牵引,通过对案例进行剖析,讲解问题背后的原理,并结合Netty源码分析...
《Netty实战》这本书是针对Java网络编程框架Netty的一本深入实践教程,旨在帮助读者掌握Netty的核心特性和实际应用。Netty是一款高性能、异步事件驱动的网络应用程序框架,广泛应用于各种分布式系统、微服务架构以及...
Netty基础,用于学习Netty,参考黑马程序员的netty教程
5. 自顶向下的学习方法:Netty的用户指南建议,如果学习者偏好从应用层面开始理解Netty,可以从架构概述章节开始,然后再回归到入门指南来实现具体的客户端和服务端的构建。 6. 示例与入门:Netty指南中的例子包括...
5. **即时聊天实战**:书中会介绍如何使用Netty构建一个简单的即时聊天应用,涉及用户注册、登录、消息发送、接收等核心功能的实现。 6. **性能优化**:Netty的性能优化策略,如内存管理、线程模型调整、缓冲区复用...
**Netty 深度解析** Netty 是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。它广泛应用于各种领域,如分布式系统、云计算、游戏服务器、大数据传输等。Netty 的设计...
《Netty实战》是针对Java开发者的一本技术指南,它深入介绍了如何利用Netty这个高性能、异步事件驱动的网络应用程序框架来构建高效且可扩展的网络应用。Netty不仅简化了网络编程的复杂性,还提供了丰富的特性和组件...
5. 扩展性和灵活性:Netty允许自定义编码解码器,支持多种数据格式和协议。 6. 快速故障恢复:通过心跳检测和自动重连机制,提高了系统的稳定性和可靠性。 三、Netty核心组件 1. Channel:表示网络连接,负责数据...