Netty In Action中文版 - 第六章:ChannelHandler
本章介绍
- ChannelPipeline
- ChannelHandlerContext
- ChannelHandler
- Inbound vs outbound(入站和出站)
接受连接或创建他们仅仅是你的应用程序的一部分,尽管这些不论什么非常重要,可是一个网络应用程序旺旺是更复杂的,须要很多其它的代码编写,如处理传入和传出的数据。Netty提供了一个强大的处理这些事情的功能,同意用户自己定义ChannelHandler的实现来处理数据。使得ChannelHandler更强大的是能够连接每一个ChannelHandler来实现任务,这有助于代码的整洁和重用。可是处理数据仅仅是ChannelHandler所做的事情之中的一个,也能够压制I/O操作,比如写请求。全部这些都能够动态实现。
6.1 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。
6.2 ChannelHandlerContext
6.2.1 通知下一个ChannelHandler
- 调用Channel的方法
- 调用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
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 状态模型
- channelUnregistered
- channelRegistered
- channelActive
- channelInactive
Channel的状态在其生命周期中变化,由于状态变化须要触发,下图显示了Channel状态变化:
6.4 ChannelHandler和其子类
6.4.1 ChannelHandler中的方法
- handlerAdded,ChannelHandler加入到实际上下文中准备处理事件
- handlerRemoved,将ChannelHandler从实际上下文中删除,不再处理事件
- exceptionCaught,处理抛出的异常
上面三个方法都须要传递ChannelHandlerContext參数,每一个ChannelHandler被加入到ChannelPipeline时会自己主动创建ChannelHandlerContext。ChannelHandlerContext同意在本地通道安全的存储和检索值。Netty还提供了一个实现了ChannelHandler的抽象类:ChannelHandlerAdapter。ChannelHandlerAdapter实现了父类的全部方法,基本上就是传递事件到ChannelPipeline中的下一个ChannelHandler直到结束。
6.4.2 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
- 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)来释放消息资源。一旦消息被传递到实际的通道,它会自己主动写入消息或在通道关闭是释放。
Netty In Action中文版 - 第六章:ChannelHandler的更多相关文章
- Netty In Action中文版 - 第四章:Transports(传输)
本章内容 Transports(传输) NIO(non-blocking IO,New IO), OIO(Old IO,blocking IO), Local(本地), Embedded(嵌入式) U ...
- Netty In Action中文版 - 第七章:编解码器Codec
http://blog.csdn.net/abc_key/article/details/38041143 本章介绍 Codec,编解码器 Decoder,解码器 Encoder,编码器 Netty提 ...
- Netty In Action中文版 - 第五章:Buffers(缓冲)
本章介绍 ByteBuf ByteBufHolder ByteBufAllocator 使用这些接口分配缓冲和运行操作 每当你须要数据传输时,它必须包括一个缓冲区.Java NIO API自带的缓冲区 ...
- Netty In Action中文版 - 第三章:Netty核心概念
在这一章我们将讨论Netty的10个核心类.清楚了解他们的结构对使用Netty非常实用.可能有一些不会再工作中用到.可是也有一些非经常常使用也非常核心,你会遇到. Bootstrap ...
- Netty In Action中文版 - 第一章:Netty介绍
本章介绍 Netty介绍 为什么要使用non-blocking IO(NIO) 堵塞IO(blocking IO)和非堵塞IO(non-blocking IO)对照 Java NIO的问题和在Nett ...
- Netty In Action中国版 - 第二章:第一Netty程序
本章介绍 获得Netty4最新的版本号 设置执行环境,以构建和执行netty程序 创建一个基于Netty的server和client 拦截和处理异常 编制和执行Nettyserver和client 本 ...
- Netty In Action中文版 - 第十五章:选择正确的线程模型
http://blog.csdn.net/abc_key/article/details/38419469 本章介绍 线程模型(thread-model) 事件循环(EventLoop) 并发(Con ...
- Learning Spark中文版--第六章--Spark高级编程(2)
Working on a Per-Partition Basis(基于分区的操作) 以每个分区为基础处理数据使我们可以避免为每个数据项重做配置工作.如打开数据库连接或者创建随机数生成器这样的操作,我们 ...
- Learning Spark中文版--第六章--Spark高级编程(1)
Introduction(介绍) 本章介绍了之前章节没有涵盖的高级Spark编程特性.我们介绍两种类型的共享变量:用来聚合信息的累加器和能有效分配较大值的广播变量.基于对RDD现有的transform ...
随机推荐
- spark1.3.1使用基础教程 分类: B8_SPARK 2015-04-28 11:10 1651人阅读 评论(0) 收藏
spark可以通过交互式命令行及编程两种方式来进行调用: 前者支持scala与python 后者支持scala.python与java 本文参考https://spark.apache.org/d ...
- 利用VS安装项目打包软件的做法
作者:朱金灿 来源:http://blog.csdn.net/clever101 昨天摸索了一下,发现使用VS安装项目来打包软件还是挺方便的. 1. 创建一个安装项目工程,如下图: 2. 设置工程属性 ...
- MySQL启动关闭添加到 /etc/init.d/mysqld
cp /data/mysql/support-files/mysql.server /etc/init.d/mysqld 然后就可以使用此命令启动/关闭 mysql: /etc/ini ...
- 【24.67%】【codeforces 551C】 GukiZ hates Boxes
time limit per test2 seconds memory limit per test256 megabytes inputstandard input outputstandard o ...
- iOS开发RunLoop学习:四:RunLoop的应用和RunLoop的面试题
一:RunLoop的应用 #import "ViewController.h" @interface ViewController () /** 注释 */ @property ( ...
- 怎样用O2O去改变充满谎言、疑虑和愤慨的维修行业
为什么千亿级的维修服务市场出不了行业巨头? 据相关统计,我国的整个维修服务市场规模可达每年数千亿元之巨(当中仅家电维修就可达近千亿规模,更遑论手机.数码.家具等维修). 相同是千亿级规模的服务行业 ...
- Stacks of Flapjacks
Stacks of Flapjacks Background Stacks and Queues are often considered the bread and butter of data s ...
- jquery-7 jquery中的文档处理方法有哪些(方法的参数表示功能增强)
jquery-7 jquery中的文档处理方法有哪些(方法的参数表示功能增强) 一.总结 一句话总结:多看参考文档,多看主干目录.一般的功能分两个方法来实现,一个对应标签,一个对应标签和事情,比如克隆 ...
- 基于 Android NDK 的学习之旅-----资源释放
做上一个项目的时候因为与C引擎交互频繁,有时候会突然莫名其妙的的整个应用程序直接挂掉.因为我是学Java 开始的,所以对主动释放内存没多大概念(GC直接帮忙回收),后查询原因才知道是因为JNI 有些对 ...
- js如何实现动态点击改变单元格颜色?
js如何实现动态点击改变单元格颜色? 一.总结 1.通过table的rows属性,遍历表格所有行,然后通过cells属性,遍历每一行中的单元格. 2.遍历的过程中,动态的为每一个单元格定义单击事件,改 ...