接受连接或创建连接只是IO应用程序的一部分,虽然这些很重要,但是一个网络应用程序往往是更复杂的需要更多的代码编写的地方是处理传入和传出的数据。
Netty 提供了一个强大的处理这些事情的功能,允许用户自定义 ChannelHandler 的实现来处理数据。ChannelHandler更强大的是可以连接每个 ChannelHandler 来实现任务,这有助于代码的整洁和重用。但是处理数据只是 ChannelHandler 所做的事情之一,也可以压制 I/O 操 作,例如写请求。所有这些都可以动态实现。
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 文档或源码。
@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", CharsetU til.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,因为这是线程安全的。
修改 ChannelPipeline 调用 ChannelHandlerContext 的 pipeline()方法能访问 ChannelPipeline, 能在运行时动态的增加、删除、替换 ChannelPipeline 中的 ChannelHandler。可以保持hannelHandlerContext 供以后使用,如外部 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);
}
}
状态模型
Netty 有一个简单但强大的状态模型, 并完美映射到ChannelInboundHandler 的各个方法。下面是 Channel 生命周期四个不同的状态:
Channel 的状态在其生命周期中变化,因为状态变化需要触发,下图显示了 Channel 状态变化:
还可以看到额外的状态变化,因为用户允许从 EventLoop 中注销 Channel 暂停事件执行,然后再重新注册。在这种情况下,你会看到多个 channelRegistered 和channelUnregistered 状态的变化,而永远只有一个 channelActive 和 channelInactive 的状态,因为一个通道在其生命周期内只能连接一次,之后就会被回收;重新连接,则是创建一个新的通道。
下图显示了从 EventLoop 中注销 Channel 后再重新注册的状态变化:
无意中发现了一个巨牛的人工智能教程,忍不住分享一下给大家。教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点这里可以跳转到教程。