Netty In Action中文版 - 第六章:ChannelHandler

本章介绍

  • ChannelPipeline
  • ChannelHandlerContext
  • ChannelHandler
  • Inbound vs outbound(入站和出站)

接受连接或创建他们仅仅是你的应用程序的一部分,尽管这些不论什么非常重要,可是一个网络应用程序旺旺是更复杂的,须要很多其它的代码编写,如处理传入和传出的数据。Netty提供了一个强大的处理这些事情的功能,同意用户自己定义ChannelHandler的实现来处理数据。使得ChannelHandler更强大的是能够连接每一个ChannelHandler来实现任务,这有助于代码的整洁和重用。可是处理数据仅仅是ChannelHandler所做的事情之中的一个,也能够压制I/O操作,比如写请求。全部这些都能够动态实现。

6.1 ChannelPipeline

ChannelPipeline是ChannelHandler实例的列表,用于处理或截获通道的接收和发送数据。ChannelPipeline提供了一种高级的截取过滤器模式,让用户能够在ChannelPipeline中全然控制一个事件及怎样处理ChannelHandler与ChannelPipeline的交互。

对于每一个新的通道,会创建一个新的ChannelPipeline并附加至通道。一旦连接,Channel和ChannelPipeline之间的耦合是永久性的。Channel不能附加其它的ChannelPipeline或从ChannelPipeline分离。

下图描写叙述了ChannelHandler在ChannelPipeline中的I/O处理,一个I/O操作能够由一个ChannelInboundHandler或ChannelOutboundHandler进行处理,并通过调用ChannelInboundHandler处理入站IO或通过ChannelOutboundHandler处理出站IO。

如上图所看到的,ChannelPipeline是ChannelHandler的一个列表;假设一个入站I/O事件被触发,这个事件会从第一个開始依次通过ChannelPipeline中的ChannelHandler;若是一个入站I/O事件,则会从最后一个開始依次通过ChannelPipeline中的ChannelHandler。ChannelHandler能够处理事件并检查类型,假设某个ChannelHandler不能处理则会跳过,并将事件传递到下一个ChannelHandler。ChannelPipeline能够动态加入、删除、替换当中的ChannelHandler,这种机制能够提高灵活性。

改动ChannelPipeline的方法:

  • addFirst(...),加入ChannelHandler在ChannelPipeline的第一个位置
  • addBefore(...),在ChannelPipeline中指定的ChannelHandler名称之前加入ChannelHandler
  • addAfter(...),在ChannelPipeline中指定的ChannelHandler名称之后加入ChannelHandler
  • addLast(ChannelHandler...),在ChannelPipeline的末尾加入ChannelHandler
  • remove(...),删除ChannelPipeline中指定的ChannelHandler
  • replace(...),替换ChannelPipeline中指定的ChannelHandler
ChannelPipeline pipeline = ch.pipeline();
FirstHandler firstHandler = new FirstHandler();
pipeline.addLast("handler1", firstHandler);
pipeline.addFirst("handler2", new SecondHandler());
pipeline.addLast("handler3", new ThirdHandler());
pipeline.remove("?handler3?");
pipeline.remove(firstHandler);
pipeline.replace("handler2", "handler4", new FourthHandler());

被加入到ChannelPipeline的ChannelHandler将通过IO-Thread处理事件,这意味了必须不能有其它的IO-Thread堵塞来影响IO的总体处理;有时候可能须要堵塞,比如JDBC。因此,Netty同意通过一个EventExecutorGroup到每个ChannelPipeline.add*方法,自己定义的事件会被包括在EventExecutorGroup中的EventExecutor来处理,默认的实现是DefaultEventExecutorGroup。

ChannelPipeline除了一些改动的方法,还有非常多其它的方法,详细是方法及使用能够看API文档或源代码。

6.2 ChannelHandlerContext

每一个ChannelHandler被加入到ChannelPipeline后,都会创建一个ChannelHandlerContext并与之创建的ChannelHandler关联绑定。ChannelHandlerContext同意ChannelHandler与其它的ChannelHandler实现进行交互,这是同样ChannelPipeline的一部分。ChannelHandlerContext不会改变加入到当中的ChannelHandler,因此它是安全的。

6.2.1 通知下一个ChannelHandler

在同样的ChannelPipeline中通过调用ChannelInboundHandler和ChannelOutboundHandler中各个方法中的一个方法来通知近期的handler,通知開始的地方取决你怎样设置。下图显示了ChannelHandlerContext、ChannelHandler、ChannelPipeline的关系:

假设你想有一些事件流所有通过ChannelPipeline,有两个不同的方法能够做到:

  • 调用Channel的方法
  • 调用ChannelPipeline的方法

这两个方法都能够让事件流所有通过ChannelPipeline。不管从头部还是尾部開始,由于它主要依赖于事件的性质。假设是一个“入站”事件,它開始于头部;若是一个“出站”事件,则開始于尾部。

以下的代码显示了一个写事件怎样通过ChannelPipeline从尾部開始:

@Override
protected void initChannel(SocketChannel ch) throws Exception {
	ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
		@Override
		public void channelActive(ChannelHandlerContext ctx) throws Exception {
			//Event via Channel
			Channel channel = ctx.channel();
			channel.write(Unpooled.copiedBuffer("netty in action", CharsetUtil.UTF_8));
			//Event via ChannelPipeline
			ChannelPipeline pipeline = ctx.pipeline();
			pipeline.write(Unpooled.copiedBuffer("netty in action", CharsetUtil.UTF_8));
		}
	});
}

下图表示通过Channel或ChannelPipeline的通知:

可能你想从ChannelPipeline的指定位置開始,不想流经整个ChannelPipeline,例如以下情况:

  • 为了节省开销,不感兴趣的ChannelHandler不让通过
  • 排除一些ChannelHandler

在这样的情况下,你能够使用ChannelHandlerContext的ChannelHandler通知起点。它使用ChannelHandlerContext运行下一个ChannelHandler。以下代码显示了直接使用ChannelHandlerContext操作:

// Get reference of ChannelHandlerContext
ChannelHandlerContext ctx = ..;
// Write buffer via ChannelHandlerContext
ctx.write(Unpooled.copiedBuffer("Netty in Action", CharsetUtil.UTF_8));

该消息流经ChannelPipeline到下一个ChannelHandler,在这样的情况下使用ChannelHandlerContext開始下一个ChannelHandler。下图显示了事件流:

如上图显示的,从指定的ChannelHandlerContext開始,跳过前面全部的ChannelHandler,使用ChannelHandlerContext操作是常见的模式,最经常使用的是从ChannelHanlder调用操作,也能够在外部使用ChannelHandlerContext,由于这是线程安全的。

6.2.2 改动ChannelPipeline

调用ChannelHandlerContext的pipeline()方法能訪问ChannelPipeline,能在执行时动态的添加、删除、替换ChannelPipeline中的ChannelHandler。能够保持ChannelHandlerContext供以后使用,如外部Handler方法触发一个事件,甚至从一个不同的线程。

以下代码显示了保存ChannelHandlerContext供之后使用或其它线程使用:

public class WriteHandler extends ChannelHandlerAdapter {
	private ChannelHandlerContext ctx;

	@Override
	public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
		this.ctx = ctx;
	}

	public void send(String msg){
		ctx.write(msg);
	}
}

请注意,ChannelHandler实例假设带有@Sharable注解则能够被加入到多个ChannelPipeline。也就是说单个ChannelHandler实例能够有多个ChannelHandlerContext,因此能够调用不同ChannelHandlerContext获取同一个ChannelHandler。假设加入不带@Sharable注解的ChannelHandler实例到多个ChannelPipeline则会抛出异常;使用@Sharable注解后的ChannelHandler必须在不同的线程和不同的通道上安全使用。怎么是不安全的使用?看以下代码:

@Sharable
public class NotSharableHandler extends ChannelInboundHandlerAdapter {

	private int count;

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		count++;
		System.out.println("channelRead(...) called the " + count + " time?");
		ctx.fireChannelRead(msg);
	}

}

上面是一个带@Sharable注解的Handler,它被多个线程使用时,里面count是不安全的,会导致count值错误。

为什么要共享ChannelHandler?使用@Sharable注解共享一个ChannelHandler在一些需求中还是有非常好的作用的,如使用一个ChannelHandler来统计连接数或来处理一些全局数据等等。

6.3 状态模型

Netty有一个简单但强大的状态模型,并完美映射到ChannelInboundHandler的各个方法。以下是Channel生命周期四个不同的状态:

  • channelUnregistered
  • channelRegistered
  • channelActive
  • channelInactive

Channel的状态在其生命周期中变化,由于状态变化须要触发,下图显示了Channel状态变化:

还能够看到额外的状态变化,由于用户同意从EventLoop中注销Channel暂停事件运行,然后再又一次注冊。在这样的情况下,你会看到多个channelRegistered和channelUnregistered状态的变化,而永远仅仅有一个channelActive和channelInactive的状态,由于一个通道在其生命周期内仅仅能连接一次,之后就会被回收;又一次连接,则是创建一个新的通道。

下图显示了从EventLoop中注销Channel后再又一次注冊的状态变化:

6.4 ChannelHandler和其子类

Netty中有3个实现了ChannelHandler接口的类,当中2个是接口,一个是抽象类。例如以下图:

6.4.1 ChannelHandler中的方法

Netty定义了良好的类型层次结构来表示不同的处理程序类型,全部的类型的父类是ChannelHandler。ChannelHandler提供了在其生命周期内加入或从ChannelPipeline中删除的方法。

  • handlerAdded,ChannelHandler加入到实际上下文中准备处理事件
  • handlerRemoved,将ChannelHandler从实际上下文中删除,不再处理事件
  • exceptionCaught,处理抛出的异常

上面三个方法都须要传递ChannelHandlerContext參数,每一个ChannelHandler被加入到ChannelPipeline时会自己主动创建ChannelHandlerContext。ChannelHandlerContext同意在本地通道安全的存储和检索值。Netty还提供了一个实现了ChannelHandler的抽象类:ChannelHandlerAdapter。ChannelHandlerAdapter实现了父类的全部方法,基本上就是传递事件到ChannelPipeline中的下一个ChannelHandler直到结束。

6.4.2 ChannelInboundHandler

ChannelInboundHandler提供了一些方法再接收数据或Channel状态改变时被调用。以下是ChannelInboundHandler的一些方法:

  • channelRegistered,ChannelHandlerContext的Channel被注冊到EventLoop;
  • channelUnregistered,ChannelHandlerContext的Channel从EventLoop中注销
  • channelActive,ChannelHandlerContext的Channel已激活
  • channelInactive,ChannelHanderContxt的Channel结束生命周期
  • channelRead,从当前Channel的对端读取消息
  • channelReadComplete,消息读取完毕后运行
  • userEventTriggered,一个用户事件被处罚
  • channelWritabilityChanged,改变通道的可写状态,能够使用Channel.isWritable()检查
  • exceptionCaught,重写父类ChannelHandler的方法,处理异常

Netty提供了一个实现了ChannelInboundHandler接口并继承ChannelHandlerAdapter的类:ChannelInboundHandlerAdapter。ChannelInboundHandlerAdapter实现了ChannelInboundHandler的全部方法,作用就是处理消息并将消息转发到ChannelPipeline中的下一个ChannelHandler。ChannelInboundHandlerAdapter的channelRead方法处理完消息后不会自己主动释放消息,若想自己主动释放收到的消息,能够使用SimpleChannelInboundHandler<I>。

看以下代码:

/**
 * 实现ChannelInboundHandlerAdapter的Handler,不会自己主动释放接收的消息对象
 * @author c.k
 *
 */
public class DiscardHandler extends ChannelInboundHandlerAdapter {
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		//手动释放消息
		ReferenceCountUtil.release(msg);
	}
}
/**
 * 继承SimpleChannelInboundHandler,会自己主动释放消息对象
 * @author c.k
 *
 */
public class SimpleDiscardHandler extends SimpleChannelInboundHandler<Object> {
	@Override
	protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
		//不须要手动释放
	}
}

假设须要其它状态改变的通知,能够重写Handler的其它方法。通常自己定义消息类型来解码字节,能够实现ChannelInboundHandler或ChannelInboundHandlerAdapter。有一个更好的解决方法,使用编解码器的框架能够非常容的实现。使用ChannelInboundHandler、ChannelInboundHandlerAdapter、SimpleChannelInboundhandler这三个中的一个来处理接收消息,使用哪一个取决于需求;大多数时候使用SimpleChannelInboundHandler处理消息,使用ChannelInboundHandlerAdapter处理其它的“入站”事件或状态改变。

ChannelInitializer用来初始化ChannelHandler,将自己定义的各种ChannelHandler加入到ChannelPipeline中。

6.4.3 ChannelOutboundHandler

ChannelOutboundHandler用来处理“出站”的数据消息。ChannelOutboundHandler提供了以下一些方法:

  • bind,Channel绑定本地地址
  • connect,Channel连接操作
  • disconnect,Channel断开连接
  • close,关闭Channel
  • deregister,注销Channel
  • read,读取消息,实际是截获ChannelHandlerContext.read()
  • write,写操作,实际是通过ChannelPipeline写消息,Channel.flush()属性到实际通道
  • flush,刷新消息到通道

ChannelOutboundHandler是ChannelHandler的子类,实现了ChannelHandler的全部方法。全部最重要的方法採取ChannelPromise,因此一旦请求停止从ChannelPipeline转发參数则必须得到通知。Netty提供了ChannelOutboundHandler的实现:ChannelOutboundHandlerAdapter。ChannelOutboundHandlerAdapter实现了父类的全部方法,而且能够依据须要重写感兴趣的方法。全部这些方法的实现,在默认情况下,都是通过调用ChannelHandlerContext的方法将事件转发到ChannelPipeline中下一个ChannelHandler。

看以下的代码:

public class DiscardOutboundHandler extends ChannelOutboundHandlerAdapter {
	@Override
	public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
		ReferenceCountUtil.release(msg);
		promise.setSuccess();
	}
}

重要的是要记得释放致远并直通ChannelPromise,若ChannelPromise没有被通知可能会导致当中一个ChannelFutureListener不被通知去处理一个消息。

假设消息被消费而且没有被传递到ChannelPipeline中的下一个ChannelOutboundHandler,那么就须要调用ReferenceCountUtil.release(message)来释放消息资源。一旦消息被传递到实际的通道,它会自己主动写入消息或在通道关闭是释放。

时间: 2024-10-29 00:27:53

Netty In Action中文版 - 第六章:ChannelHandler的相关文章

《Netty In Action中文版》第二章:第一个Netty程序

注:本篇内容出自<Netty In Action>一书:         注:本人原创译文,转载请注明出处! 本章介绍 获取Netty4最新版本 设置运行环境来构建和运行netty程序 创建一个基于Netty的服务器和客户端 拦截和处理异常 编写和运行Netty服务器和客户端 本章将简单介绍Netty的核心概念,这个狠心概念就是学习Netty是如何拦截和处理异常,对于刚开始学习netty的读者,利用netty的异常拦截机制来调试程序问题很有帮助.本章还会介绍其他一些核心概念,如服务器和客户端的

Netty In Action中文版 - 第四章:Transports(传输)

本章内容 Transports(传输) NIO(non-blocking IO,New IO), OIO(Old IO,blocking IO), Local(本地), Embedded(嵌入式) Use-case(用例) APIs(接口) 网络应用程序一个非常重要的工作是数据传输. 数据传输的过程不一样取决是使用哪种交通工具,可是传输的方式是一样的:都是以字节码传输.Java开发网络程序数据传输的过程和方式是被抽象了的.我们不须要关注底层接口.仅仅须要使用Java API或其它网络框架如Net

Netty In Action中文版 学习第一章的重点知识

第一章 大多数都是一些名词,SSL/TLS和Starttls支持,回调,聚合,分散,这些我也不是很明白,也百度了一下这些基础概念,大家也可以去看看实际的内容以及相关的代码,由于本人也是新手对这些词也理解不深刻不在此细说. 下面是一个jar文件搜索的比较好的网址拿出来分享给大家 http://www.findjar.com/index.x?query=xlstojson jar包的搜索地 下面就是工具,作为新人大多数都说用Eclipse就够了,我由于想看开源的源码所以选择了MyEclipse 10

Netty In Action中国版 - 第二章:第一Netty程序

本章介绍 获得Netty4最新的版本号 设置执行环境,以构建和执行netty程序 创建一个基于Netty的server和client 拦截和处理异常 编制和执行Nettyserver和client 本章将简介Netty的核心概念,这个狠心概念就是学习Netty是怎样拦截和处理异常.对于刚開始学习netty的读者.利用netty的异常拦截机制来调试程序问题非常有帮助.本章还会介绍其它一些核心概念.如server和client的启动以及分离通道的处理程序.本章学习一些基础以便后面章节的深入学习. 本

Netty in Action (十六) 第六章节 第二部分 ChannelHandlerContext和异常处理

6.3 Interface ChannelHandlerContext 一个ChannelHandlerContext代表了一个ChannelHandler和ChannelPipeline之间的关系,ChannelHandlerContext创建于ChannelHandler被载入到ChannelPipeline的时候,ChannelHandlerContext主要功能是管理在同一ChannelPipeline中各个ChannelHandler的交互 ChannelHandlerContext有

Netty源码分析第6章(解码器)----&gt;第3节: 行解码器

Netty源码分析第六章: 解码器 第三节: 行解码器 这一小节了解下行解码器LineBasedFrameDecoder, 行解码器的功能是一个字节流, 以\r\n或者直接以\n结尾进行解码, 也就是以换行符为分隔进行解析 同样, 这个解码器也继承了ByteToMessageDecoder 首先看其参数: //数据包的最大长度, 超过该长度会进行丢弃模式 private final int maxLength; //超出最大长度是否要抛出异常 private final boolean fail

Netty源码分析第6章(解码器)----&gt;第1节: ByteToMessageDecoder

Netty源码分析第六章: 解码器 概述: 在我们上一个章节遗留过一个问题, 就是如果Server在读取客户端的数据的时候, 如果一次读取不完整, 就触发channelRead事件, 那么Netty是如何处理这类问题的, 在这一章中, 会对此做详细剖析 之前的章节我们学习过pipeline, 事件在pipeline中传递, handler可以将事件截取并对其处理, 而之后剖析的编解码器, 其实就是一个handler, 截取byteBuf中的字节, 然后组建成业务需要的数据进行继续传播 编码器,

Netty源码分析第6章(解码器)----&gt;第2节: 固定长度解码器

Netty源码分析第六章: 解码器 第二节: 固定长度解码器 上一小节我们了解到, 解码器需要继承ByteToMessageDecoder, 并重写decode方法, 将解析出来的对象放入集合中集合, ByteToMessageDecoder中可以将解析出来的对象向下进行传播, 这一小节带大家剖析一个最简单的解码器FixedLengthFrameDecoder, 从它入手了解码器的相关原理 FixedLengthFrameDecoder是一个固定长度的解码器, 功能就是根据固定长度, 截取固定大

Solr In Action 中文版 第一章 (二)

Solr到底是什么? 在本节中,我们通过从头设计一个搜索应用来介绍Solr的关键组件.这个过程将有助于你理解Solr的功能,以及设计这些功能的初衷.不过在我们开始介绍Solr的功能特性之前,还是要先澄清一下Solr并不具有的一些性质: 1)  Solr并不是一个像Google或是Bing那样的web搜索引擎 2)  Solr和网站优化中经常提到的搜索引擎SEO优化没有任何关系 好了,现在假设我们准备为潜在的购房客户设计一个不动产搜索的网络应用.该应用的核心用例场景是通过网页浏览器来搜索全美国范围