Netty学习(二)

Channel Handlers

Netty的ChannelHandler是应用程序中处理最多的。Netty应用至少有一个ChannelHandler参与。
那么ChannelHandler是什么?
我们可以理解为ChannelHandler是一段执行业务逻辑处理数据的代码,它们来来往往的通过ChannelPipeline。
Netty中有两个方向的数据流,入站(ChannelInboundHandler)和出站(ChannelOutboundHandler)之间有一个明显的区别:若数据是从用户应用程序到远程主机则是“出站(outbound)”,相反若数据时从远程主机到用户应用程序则是“入站(inbound)”。
为了使数据从一端到达另一端,一个或多个ChannelHandler将以某种方式操作数据。这些ChannelHandler会在程序的“引导”阶段被添加ChannelPipeline中,并且被添加的顺序将决定处理数据的顺序。ChannelPipeline的作用我们可以理解为用来管理ChannelHandler的一个容器,每个ChannelHandler处理各自的数据(例如入站数据只能由ChannelInboundHandler处理),处理完成后将转换的数据放到ChannelPipeline中交给下一个ChannelHandler继续处理,直到最后一个ChannelHandler处理完成。
Netty学习(二)_第1张图片
上图显示ChannelInboundHandler和ChannelOutboundHandler都要经过相同的ChannelPipeline。
在ChannelPipeline中,如果消息被读取或有任何其他的入站事件,消息将从ChannelPipeline的头部开始传递给第一个ChannelInboundHandler,这个ChannelInboundHandler可以处理该消息或将消息传递到下一个ChannelInboundHandler中,一旦在ChannelPipeline中没有剩余的ChannelInboundHandler后,ChannelPipeline就知道消息已被所有的Handler处理完成了。
当一个ChannelHandler添加到ChannelPipeline中时获得一个ChannelHandlerContext。通常是安全的获得这个对象的引用,但是当一个数据报协议如UDP时这是不正确的,这个对象可以在之后用来获取底层通道,因为要用它来read/write消息,因此通道会保留。也就是说Netty中发送消息有两种方法:直接写入通道或写入ChannelHandlerContext对象。这两种方法的主要区别如下:

  • 直接写入通道导致处理消息从ChannelPipeline的尾部开始
  • 写入ChannelHandlerContext对象导致处理消息从ChannelPipeline的下一个handler开始

Transport API

传输API的核心是Channel接口,它用于所有出站的操作。Channel接口的类层次结构如下

Netty学习(二)_第2张图片
如上图所示,每个Channel都会分配一个ChannelPipeline和ChannelConfig。ChannelConfig负责设置并存储配置,并允许在运行期间更新它们。传输一般有特定的配置设置,只作用于传输,没有其他的实现。ChannelPipeline容纳了使用的ChannelHandler实例,这些ChannelHandler将处理通道传递的“入站”和“出站”数据。ChannelHandler的实现允许你改变数据状态和传输数据
现在我们可以使用ChannelHandler做下面一些事情:

  • 传输数据时,将数据从一种格式转换到另一种格式
  • 异常通知
  • Channel变为有效或无效时获得通知
  • Channel被注册或从EventLoop中注销时获得通知
  • 通知用户特定事件
    这些ChannelHandler实例添加到ChannelPipeline中,在ChannelPipeline中按顺序逐个执行。它类似于一个链条,有使用过Servlet的读者可能会更容易理解。
    ChannelPipeline实现了拦截过滤器模式,这意味着我们连接不同的ChannelHandler来拦截并处理经过ChannelPipeline的数据或事件。可以把ChannelPipeline想象成UNIX管道,它允许不同的命令链(ChannelHandler相当于命令)。你还可以在运行时根据需要添加ChannelHandler实例到ChannelPipeline或从ChannelPipeline中删除,这能帮助我们构建高度灵活的Netty程序。此外,访问指定的ChannelPipeline和ChannelConfig,你能在Channel自身上进行操作。Channel提供了很多方法,如下列表:
    eventLoop(),返回分配给Channel的EventLoop
    pipeline(),返回分配给Channel的ChannelPipeline
    isActive(),返回Channel是否激活,已激活说明与远程连接对等
    localAddress(),返回已绑定的本地SocketAddress
    remoteAddress(),返回已绑定的远程SocketAddress
    write(),写数据到远程客户端,数据通过ChannelPipeline传输过去
    写数据到远程已连接客户端可以调用Channel.write()方法,如下代码:
1.Channel channel = ...  
2.//Create ByteBuf that holds data to write  
3.ByteBuf buf = Unpooled.copiedBuffer("your data", CharsetUtil.UTF_8);  
4.//Write data  
5.ChannelFuture cf = channel.write(buf);  
6.//Add ChannelFutureListener to get notified after write completes  
7.cf.addListener(new ChannelFutureListener() {  
8.    @Override  
9.    public void operationComplete(ChannelFuture future) {  
10.        //Write operation completes without error  
11.        if (future.isSuccess()) {  
12.            System.out.println(.Write successful.);  
13.        } else {  
14.            //Write operation completed but because of error  
15.            System.err.println(.Write error.);  
16.            future.cause().printStacktrace();  
17.        }  
18.    }  
19.});  

Channel是线程安全(thread-safe)的,它可以被多个不同的线程安全的操作,在多线程环境下,所有的方法都是安全的。正因为Channel是安全的,我们存储对Channel的引用,并在学习的时候使用它写入数据到远程已连接的客户端,使用多线程也是如此。
Netty包含的传输实现
Netty自带了一些传输协议的实现,虽然没有支持所有的传输协议,但是其自带的已足够我们来使用。Netty应用程序的传输协议依赖于底层协议,本节我们将学习Netty中的传输协议。
Netty中的传输方式有如下几种:
NIO,io.netty.channel.socket.nio,基于java.nio.channels的工具包,使用选择器作为基础的方法。
OIO,io.netty.channel.socket.oio,基于java.net的工具包,使用阻塞流。
Local,io.netty.channel.local,用来在虚拟机之间本地通信。
Embedded,io.netty.channel.embedded,嵌入传输,它允许在没有真正网络的运输中使用ChannelHandler,可以非常有用的来测试ChannelHandler的实现。
那他们在什么情况下使用呢?
OIO,在低连接数、需要低延迟时、阻塞时使用
NIO,在高连接数时使用
Local,在同一个JVM内通信时使用
Embedded,测试ChannelHandler时使用

ChannelHandler

ChannelHandler中的方法
Netty定义了良好的类型层次结构来表示不同的处理程序类型,所有的类型的父类是ChannelHandler。ChannelHandler提供了在其生命周期内添加或从ChannelPipeline中删除的方法。
handlerAdded,ChannelHandler添加到实际上下文中准备处理事件
handlerRemoved,将ChannelHandler从实际上下文中删除,不再处理事件
exceptionCaught,处理抛出的异常
上面三个方法都需要传递ChannelHandlerContext参数,每个ChannelHandler被添加到ChannelPipeline时会自动创建ChannelHandlerContext。ChannelHandlerContext允许在本地通道安全的存储和检索值。Netty还提供了一个实现了ChannelHandler的抽象类:ChannelHandlerAdapter。ChannelHandlerAdapter实现了父类的所有方法,基本上就是传递事件到ChannelPipeline中的下一个ChannelHandler直到结束。
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

1./** 
2. * 实现ChannelInboundHandlerAdapter的Handler,不会自动释放接收的消息对象 
3. * @author c.k 
4. * 
5. */  
6.public class DiscardHandler extends ChannelInboundHandlerAdapter {  
7.    @Override  
8.    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {  
9.        //手动释放消息  
10.        ReferenceCountUtil.release(msg);  
11.    }  
12.}  
1./** 
2. * 继承SimpleChannelInboundHandler,会自动释放消息对象 
3. * @author c.k 
4. * 
5. */  
6.public class SimpleDiscardHandler extends SimpleChannelInboundHandler<Object> {  
7.    @Override  
8.    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {  
9.        //不需要手动释放  
10.    }  
11.}  

如果需要其他状态改变的通知,可以重写Handler的其他方法。通常自定义消息类型来解码字节,可以实现ChannelInboundHandler或ChannelInboundHandlerAdapter。有一个更好的解决方法,使用编解码器的框架可以很容的实现。使用ChannelInboundHandler、ChannelInboundHandlerAdapter、SimpleChannelInboundhandler这三个中的一个来处理接收消息,使用哪一个取决于需求;大多数时候使用SimpleChannelInboundHandler处理消息,使用ChannelInboundHandlerAdapter处理其他的“入站”事件或状态改变。
ChannelInitializer用来初始化ChannelHandler,将自定义的各种ChannelHandler添加到ChannelPipeline中。

WebSocket

HTTP是不错的协议,但是如果需要实时发布信息怎么做?有个做法就是客户端一直轮询请求服务器,这种方式虽然可以达到目的,但是其缺点很多,也不是优秀的解决方案,为了解决这个问题,便出现了WebSocket。
WebSocket允许数据双向传输,而不需要请求-响应模式。早期的WebSocket只能发送文本数据,然后现在不仅可以发送文本数据,也可以发送二进制数据,这使得可以使用WebSocket构建你想要的程序。下图是WebSocket的通信示例图:
Netty学习(二)_第3张图片
在应用程序中添加WebSocket支持很容易,Netty附带了WebSocket的支持,通过ChannelHandler来实现。使用WebSocket有不同的消息类型需要处理。下面列表列出了Netty中WebSocket类型:
BinaryWebSocketFrame,包含二进制数据
TextWebSocketFrame,包含文本数据
ContinuationWebSocketFrame,包含二进制数据或文本数据,BinaryWebSocketFrame和TextWebSocketFrame的结合体
CloseWebSocketFrame,WebSocketFrame代表一个关闭请求,包含关闭状态码和短语
PingWebSocketFrame,WebSocketFrame要求PongWebSocketFrame发送数据
PongWebSocketFrame,WebSocketFrame要求PingWebSocketFrame响应
为了简化,我们只看看如何使用WebSocket服务器。客户端使用可以看Netty自带的WebSocket例子。
Netty提供了许多方法来使用WebSocket,但最简单常用的方法是使用WebSocketServerProtocolHandler。看下面代码:

   1./** 
2. * WebSocket Server,若想使用SSL加密,将SslHandler加载ChannelPipeline的最前面即可 
3. * @author c.k 
4. * 
5. */  
6.public class WebSocketServerInitializer extends ChannelInitializer<Channel> {  
7.  
8.    @Override  
9.    protected void initChannel(Channel ch) throws Exception {  
10.        ch.pipeline().addLast(new HttpServerCodec(),   
11.                new HttpObjectAggregator(65536),  
12.                new WebSocketServerProtocolHandler("/websocket"),  
13.                new TextFrameHandler(),  
14.                new BinaryFrameHandler(),  
15.                new ContinuationFrameHandler());  
16.    }  
17.  
18.    public static final class TextFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {  
19.        @Override  
20.        protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {  
21.            // handler text frame  
22.        }  
23.    }  
24.      
25.    public static final class BinaryFrameHandler extends SimpleChannelInboundHandler<BinaryWebSocketFrame>{  
26.        @Override  
27.        protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) throws Exception {  
28.            //handler binary frame  
29.        }  
30.    }  
31.      
32.    public static final class ContinuationFrameHandler extends SimpleChannelInboundHandler<ContinuationWebSocketFrame>{  
33.        @Override  
34.        protected void channelRead0(ChannelHandlerContext ctx, ContinuationWebSocketFrame msg) throws Exception {  
35.            //handler continuation frame  
36.        }  
37.    }  
38.}  

处理空闲连接和超时
处理空闲连接和超时是网络应用程序的核心部分。当发送一条消息后,可以检测连接是否还处于活跃状态,若很长时间没用了就可以断开连接。Netty提供了很好的解决方案,有三种不同的ChannelHandler处理闲置和超时连接:
IdleStateHandler,当一个通道没有进行读写或运行了一段时间后出发IdleStateEvent
ReadTimeoutHandler,在指定时间内没有接收到任何数据将抛出ReadTimeoutException
WriteTimeoutHandler,在指定时间内有写入数据将抛出WriteTimeoutException
最常用的是IdleStateHandler,下面代码显示了如何使用IdleStateHandler,如果60秒内没有接收数据或发送数据,操作将失败,连接将关闭:

1.public class IdleStateHandlerInitializer extends ChannelInitializer<Channel> {  
2.  
3.    @Override  
4.    protected void initChannel(Channel ch) throws Exception {  
5.        ChannelPipeline pipeline = ch.pipeline();  
6.        pipeline.addLast(new IdleStateHandler(0, 0, 60, TimeUnit.SECONDS));  
7.        pipeline.addLast(new HeartbeatHandler());  
8.    }  
9.  
10.    public static final class HeartbeatHandler extends ChannelInboundHandlerAdapter {  
11.        private static final ByteBuf HEARTBEAT_SEQUENCE = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer(  
12.                "HEARTBEAT", CharsetUtil.UTF_8));  
13.  
14.        @Override  
15.        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {  
16.            if (evt instanceof IdleStateEvent) {  
17.                ctx.writeAndFlush(HEARTBEAT_SEQUENCE.duplicate()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);  
18.            } else {  
19.                super.userEventTriggered(ctx, evt);  
20.            }  
21.        }  
22.    }  
23.}  

序列化数据

开发网络程序过程中,很多时候需要传输结构化对象数据POJO,Java中提供了ObjectInputStream和ObjectOutputStream及其他的一些对象序列化接口。Netty中提供基于JDK序列化接口的序列化接口。
普通的JDK序列化
如果你使用ObjectInputStream和ObjectOutputStream,并且需要保持兼容性,不想有外部依赖,那么JDK的序列化是首选。Netty提供了下面的一些接口,这些接口放在io.netty.handler.codec.serialization包下面:
CompatibleObjectEncoder
CompactObjectInputStream
CompactObjectOutputStream
ObjectEncoder
ObjectDecoder
ObjectEncoderOutputStream
ObjectDecoderInputStream

你可能感兴趣的:(Netty,Netty)