夜读

程序媛

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

 

      Netty是一个非阻塞的,时间驱动的网络框架。

      一个Netty程序开始于一个Bootstrap类,Bootstrap类是Netty提供的一个可以通过简单配置来设置或“引导”程序的重要的类。

      Netty中设计了Handlers来处理特定的“event”和设置Netty中的事件,从而来处理多个协议和数据。可以自定义Handler用来将Obeject转换成byte[],或者将byte[]转换成Object,或者定义handler处理异常。

      ChannelInboundHandler用来接收消息,当程序需要返回信息时,在ChannelInboundHandler里write/flush数据。可以认为程序的业务逻辑都是在ChannelInboundHandler里处理的,业务逻辑的生命周期是在ChannelInboundHandler中。

       连接客户端或者绑定服务器端需要知道如何发送或接收消息,可以通过不同类型的Handler来实现。Netty通过ChannelInitializer来配置Handler,ChannelInitializer通过ChannelPipeline来添加Handler。ChannelInitializer本身也是一个ChannelHander,在添加完其他的Handler之后会自己从ChannelPipeline删除自己。

       Netty中所有的I/O操作都是异步的,Netty使用Futrures或者ChannelFutures来达到注册监听异步操作是否成功的目的。Furture注册一个监听,当操作成功或失败时会通知,ChannelFutures封装一个操作的相关信息,操作被执行时,会立刻返回ChannelFutures。

      Netty中的EventLoopGroup包含一个或多个EventLoop,而EventLoop就是一个Channel执行实际工作的线程。EventLoop总是绑定一个单一的线程,在其生命周期内不会改变。这保证了在Netty I/O操作中,程序不需要同步,因为一个指定通道的所有I/O始终由同一个线程来执行。很多Channel会共享同一个EventLoop。

      Bootstrap用来连接远程主机,有1个EventLoopGroup(单例的)。

      ServerBootstrap用来绑定本地端口,有2个EventLoopGroup(实际上使用的是相同的实例)。

      ServerBootstrap在服务器端监听一个端口,轮询客户端的“Bootstrap”或DatagramChannel是否连接服务器,通常需要调用“Bootstrap”类的connect()方法,但是也可以先调用bind()再调用connect()进行连接,之后使用的Channel包含在bind()返回的ChannelFuture中。

       一个ServerBootstrap可以认为有2个channel组,第一组包含一个单例的ServerChannel,代表持有了一个保存本地端口的socket;第二组包含所有的Channel,代表服务器已经接受了的链接。

             上图中EventLoopGroup A的唯一目的就是接收所有连接请求,然后交给EventLoopGroup B。

             EventLoopGroup将是程序性能的瓶颈,因为时间循环忙于处理链接请求,没有多余的资源来处理业务逻辑,若有两个EventLoops,即使是在高负载的情况下,所有的连接也会被接收,因为EventLoop接收链接不会和已连接并进行业务逻辑处理的EventLoop共享资源。

 

             ChannelHandler是一段执行业务逻辑的代码,它们来来往往的通过ChannelPipeline.

 

              Netty中有两个方向的数据流,入站(ChannelInboundHandler)和出站(ChannelOutboundHandler),若数据是从应用程序到远程主机,则是出站(outbound),如果是从远程主机到应用程序则是入站(Inbound)。

   

             ChannelPipleline是一个管理ChannelHandler的容器,ChannelHandler在程序Bootstrap阶段被添加到ChannelPipleline中,被添加的顺序将决定处理数据的顺序,

 

             访问非堆缓冲区ByteBuf的数组会导致UnsupportedOperationException,可以使用ByteBuf.hasArray()来检查是否支持访问数组。ByteBuf.hasArray()的原理

             Direct Buffer(直接缓冲区)在堆之外直接分配内存。直接缓冲区不会占用堆空间容量,使用时应该考虑到应用程序要使用的最大内存容量以及如何限制它。直接缓冲区在使用Socket传递数据时性能很好,因为若使用间接缓冲区,JVM会先将数据复制到直接缓冲区再进行传递。直接缓冲区不支持数组访问数据。

             复合缓冲区,我们可以创建多个不同的ByteBuf,然后提供一个这些ByteBuf组合的视图。Netty提供了CompositeByteBuf类来处理复合缓冲区,CompositeByteBuf只是一个视图,CompositeByteBuf.hasArray()总是返回false。一条消息由header和body两部分组成,将header和body组装成一条消息发送出去,可能body相同,只是header不同,使用
CompositeByteBuf就不用每次都重新分配一个新的缓冲区。

      

             注意通过索引访问时不会推进读索引和写索引,我们可以通过ByteBuf的readerIndex()或writerIndex()。ByteBuf提供两个指针变量支付读和写操作,读操作是使用readerIndex(),写操作时使用writerIndex()。这和JDK的ByteBuffer不同,ByteBuffer只有一个方法来设置索引,所以需要使用flip()方法来切换读和写模式。

             调用ByteBuf.clear()可以设置readerIndex和writerIndex为0,clear()不会清除缓冲区的内容,只是将两个索引值设置为0。请注意
ByteBuf.clear()与JDK的ByteBuffer.clear()的语义不同。ByteBuf.discardReadBytes()可以用来清空ByteBuf中已读取的数据,从而使ByteBuf有多余的空间容纳新的数据,但是
discardReadBytes()可能会涉及内存复制,因为它需要移动ByteBuf中可读的字节到开始位置,这样的操作会影响性能,一般在需要马上释放内存的时候使用收益会比较大。和discardReadBytes()相比,clear()是便宜的,因为clear()不会复制任何内存。

             Netty使用reference-counting(引用计数)的时候知道安全释放Buf和其他资源,虽然知道Netty有效的使用引用计数,这都是自动完成的。

    

              获取ByteBufAllocator对象很容易,可以从Channel的alloc()获取,也可以从ChannelHandlerContext的alloc()获取。

              永远只有一个channelActive和channelInactive的状态,因为一个通道在其生命周期内只能连接一次,之后就会被回收;重新连接,则是创建一个新的通道。

              ChannelHandlerAdapter实现了父类的所有方法,基本上就是传递事件到ChannelPipeline中的下一个ChannelHandler直到结束ChannelInboundHandlerAdapter的channelRead方法处理完消息后不会自动释放消息,若想自动释放收到的消息,可以使用SimpleChannelInboundHandler。

 

             如果消息被消费并且没有被传递到ChannelPipeline中的下一个ChannelOutboundHandler,那么就需要调用ReferenceCountUtil.release(message)来释放消息资源。一旦消息被传递到实际的通道,它会自动写入消息或在通道关闭时释放。重要的是要记得释放致远并通知ChannelPromise,若ChannelPromise没有被通知可能会导致其中一个ChannelFutureListener不被通知去处理一个消息。例如,应当使用如下的方式:

public class DiscardOutBoundler extends ChannelOutboundHandlerAdapter{
       @override
       public void write(ChannelHandlerContext cxt , Object msg, ChannlePromise promise) throws Exception{
       ReferenceCountUtil.release(msg);
       promise.setSuccess();
}
}

            消息被编码后解码后会自动通过ReferenceCountUtil.release(message)释放,如果不想释放消息可以使用ReferenceCountUtil.retain(message)

  

 

             有时候需要从另一个Channel引导客户端,例如写一个代理或需要从其他系统检索数据。从其他系统获取数据时比较常见的,有很 多Netty应用程序必须要和企业现有的系统集成,如Netty程序与内部系统进行身份验证,查询数据库等。当然,你可以创建一个新的引导,这样做没有什么不妥,只是效率不高,因为要为新创建的客户端通道使用另一个EventLoop,如 果需要在已接受的通道和客户端通道之间交换数据则需要切换上下文线程。Netty对这方面进行了优化,可以讲已接受的通道通过 eventLoop(...)传递到EventLoop,从而使客户端通道在相同的EventLoop里运行。这消除了额外的上下文切换工作,因为EventLoop继承 于EventLoopGroup。除了消除上下文切换,还可以在不需要创建多个线程的情况下使用引导。 一个EventLoop由一个线程执行,共享EventLoop可以确定所有的Channel都分配给同一线程的 EventLoop,这样就避免了不同线程之间切换上下文。

 

      比较麻烦的是创建通道后不得不手动配置每个通道,为了避免这种情况,Netty提供了ChannelOption来帮助引导配置。这些选项会 自动应用到引导创建的所有通道,可用的各种选项可以配置底层连接的详细信息,如通道“keep-alive(保持活跃)”或“timeout(超时)”的特性。Netty提供了通道属性(channel attributes)。属性可以将数据和通道以一个安全的方式关联,这些属性只是作用于客户端和服务器的通道。例如,例如客户端请求web服务器应 用程序,为了跟踪通道属于哪个用户,应用程序可以存储用的ID作为通道的一个属性。任何对象或数据都可以使用属性被关联到一个通道。使用ChannelOption和属性可以让事情变得很简单,例如Netty WebSocket服务器根据用户自动路由消息,通过使用属性,应用程序 能在通道存储用户ID以确定消息应该发送到哪里。应用程序可以通过使用一个通道选项进一步自动化,给定时间内没有收到消息将自动断开连接。

 

posted on 2015-09-07 10:59  Lucky_Liu  阅读(1396)  评论(0编辑  收藏  举报