Netty核心组件及基础服务构建

Posted wuzhenzhao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty核心组件及基础服务构建相关的知识,希望对你有一定的参考价值。

Netty是什么:

  1. 异步事件驱动框架,用于快速开发高i性能服务端和客户端
  2. 封装了JDK底层BIO和NIO模型,提供高度可用的API
  3. 自带编码解码器解决拆包粘包问题,用户只用关心业务逻辑
  4. 精心设计的Reactor线程模型支持高并发海量连接
  5. 自带协议栈,无需用户关心
  Netty 是一款提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。也就是说,Netty 是一个基于 NIO 的客户、服务器端编程框架,使用 Netty 可以确保你快速和简单地开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty 相当简化和流线化了网络应用的编程开发过程,例如,TCP 和 UDP 的 socket 服务开发。

Netty具有如下特性:

  • 设计:统一的API,支持多种传输类型,阻塞和非阻塞的,简单而强大的线程模型,真正的无连接数据报套接字支持,链接逻辑组件以支持复用。
  • 易于使用:详实的 Javadoc 和大量的示例集不需要超过JdK 1.6+的依赖。
  • 性能:拥有比 Java 的核心 API 更高的吞吐量以及更低的延迟,得益于池化和复用,拥有更低的资源消耗,最少的内存复制。
  • 健壮性:不会因为慢速、快速或者超载的连接而导致 OutOfMemoryError ,消除在高速网络中 NIO 应用程序常见的不公平读/写比率。
  • 安全性:完整的 SSL/TLS 以及 StartTLs 支持,可用于受限环境下,如 Applet 和 OSGI。
  • 社区驱动:发布快速而且频繁。

Netty核心组件:

  为了后期更好地理解和进一步深入 Netty,有必要总体认识一下 Netty 所用到的核心组件以及他们在整个 Netty 架构中是如何协调工作的。Nettty 有如下几个核心组件:

  • Bootstrap 和 ServerBootstrap
  • Channel
  • ChannelHandler
  • ChannelPipeline
  • EventLoop
  • ChannelFuture

1.Bootstrap或者ServerBootstrap,一个Netty应用通常由一个Bootstrap开始,它主要作用是配置整个Netty程序,串联起各个组件。

2.Channel:Channel 是 Netty 网络操作抽象类,它除了包括基本的 I/O 操作,如 bind、connect、read、write 之外,还包括了 Netty 框架相关的一些功能,如获取该 Channel的 EventLoop。在传统的网络编程中,作为核心类的 Socket ,它对程序员来说并不是那么友好,直接使用其成本还是稍微高了点。而Netty 的 Channel 则提供的一系列的 API :它大大降低了直接与 Socket 进行操作的复杂性。而相对于原生 NIO 的 Channel,Netty 的 Channel 具有如下优势:

  1. 在 Channel 接口层,采用 Facade 模式进行统一封装,将网络 I/O 操作、网络 I/O 相关联的其他操作封装起来,统一对外提供。

  2. Channel 接口的定义尽量大而全,为 SocketChannel 和 ServerSocketChannel 提供统一的视图,由不同子类实现不同的功能,公共功能在抽象父类中实现,最大程度地实现功能和接口的重用。

  3. 具体实现采用聚合而非包含的方式,将相关的功能类聚合在 Channel 中,有 Channel 统一负责和调度,功能实现更加灵活。

  Channel 与 socket 的关系:

  在 Netty 中 Channel 有两种,对应客户端套接字通道NiosocketChannel,内部管理java.nio.channels.SocketChannel 套接字,对应服务器端监听套接字通道NioServerSocketChannel,其内部管理自己的 java.nio.channels.ServerSocketChannel 套接字。也就是 Channel 是对 socket 的装饰或者门面,其封装了对socket 的原子操作。

3.ChannelHandler:ChannelHandler 为 Netty 中最核心的组件,它充当了所有处理入站和出站数据的应用程序逻辑的容器。ChannelHandler 主要用来处理各种事件,这里的事件很广泛,比如可以是连接、数据接收、异常、数据转换等。ChannelHandler 有两个核心子类 ChannelInboundHandler 和 ChannelOutboundHandler,其中ChannelInboundHandler 用于接收、处理入站数据和事件,而 ChannelOutboundHandler 则相反。

4.ChannelPipeline:ChannelPipeline 为 ChannelHandler 链提供了一个容器并定义了用于沿着链传播入站和出站事件流的 API。一个数据或者事件可能会被多个 Handler 处理,在这个过程中,数据或者事件经流 ChannelPipeline,由 ChannelHandler 处理。在这个处理过程中,一个 ChannelHandler 接收数据后处理完成后交给下一个 ChannelHandler,或者什么都不做直接交给下一个 ChannelHandler。

技术图片

  当一个数据流进入 ChannlePipeline 时,它会从 ChannelPipeline 头部开始传给第一个 ChannelInboundHandler ,当第一个处理完后再传给下一个,一直传递到管道的尾部。与之相对应的是,当数据被写出时,它会从管道的尾部开始,先经过管道尾部的 “最后” 一个ChannelOutboundHandler,当它处理完成后会传递给前一个ChannelOutboundHandler 。当 ChannelHandler 被添加到 ChannelPipeline 时,它将会被分配一个 ChannelHandlerContext,它代表了 ChannelHandler 和 ChannelPipeline之间的绑定。其中 ChannelHandler 添加到 ChannelPipeline 过程如下:

  1. 一个 ChannelInitializer 的实现被注册到了 ServerBootStrap中

  2. 当 ChannelInitializer.initChannel() 方法被调用时,ChannelInitializer 将在 ChannelPipeline 中安装一组自定义的 ChannelHandler

  3. ChannelInitializer 将它自己从 ChannelPipeline 中移除

5.EventLoop:Netty 基于事件驱动模型,使用不同的事件来通知我们状态的改变或者操作状态的改变。它定义了在整个连接的生命周期里当有事件发生的时候处理的核心抽象。Channel 为Netty 网络操作抽象类,EventLoop 主要是为Channel 处理 I/O 操作,两者配合参与 I/O 操作。下图是Channel、EventLoop、Thread、EventLoopGroup之间的关系(摘自《Netty In Action》):

技术图片
  • 一个 EventLoopGroup(Boos线程池,work线程池的分组概念) 包含一个或多个 EventLoop。
  • 一个 EventLoop 在它的生命周期内只能与一个Thread绑定。
  • 所有有 EnventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理。
  • 一个 Channel 在它的生命周期内只能注册与一个 EventLoop。
  • 一个 EventLoop 可被分配至一个或多个 Channel 。

  当一个连接到达时,Netty 就会注册一个 Channel,然后从 EventLoopGroup 中分配一个 EventLoop 绑定到这个Channel上,在该Channel的整个生命周期中都是有这个绑定的 EventLoop 来服务的。

6.ChannelFuture:Netty 为异步非阻塞,即所有的 I/O 操作都为异步的,因此,我们不能立刻得知消息是否已经被处理了。Netty 提供了 ChannelFuture 接口,通过该接口的 addListener() 方法注册一个 ChannelFutureListener,当操作执行成功或者失败时,监听就会自动触发返回结果。

  通过了解相应的组件,接下去先简单看一下Netty的基本使用,同样的市服务端与客户端的交互。

  服务端:
public class NettyServer  

    private static final String IP = "127.0.0.1";
    private static final int port = 6666;
    private static final int BIZGROUPSIZE =  Runtime.getRuntime().availableProcessors() * 2;
    private static final int BIZTHREADSIZE = 100;
    //创建两个EventLoopGroup对象,创建boss线程组 ?于服务端接受客户端的连接
    private static final EventLoopGroup bossGroup = new NioEventLoopGroup(BIZGROUPSIZE);
    //创建 worker 线程组 ?于进? SocketChannel 的数据读写
    private static final EventLoopGroup workGroup = new NioEventLoopGroup(BIZTHREADSIZE);

    public static void start() throws Exception 

        //启动类初始化
        ServerBootstrap serverBootstrap = initServerBootstrap();
        // 绑定端?,并同步等待成功,即启动服务端
        ChannelFuture channelFuture = serverBootstrap.bind(IP, port).sync();
        //成功绑定到端口之后,给channel增加一个 管道关闭的监听器并同步阻塞,直到channel关闭,线程才会往下执行,结束进程。
        channelFuture.channel().closeFuture().sync();
        System.out.println("server start");

    

    private static ServerBootstrap initServerBootstrap() 
        //一个Netty应用通常由一个Bootstrap开始
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        //添加两个组,设置使?的EventLoopGroup
        serverBootstrap.group(bossGroup,workGroup)
                //初始化 channel,设置要被实例化的为 NioServerSocketChannel 类
                .channel(NioServerSocketChannel.class)
                //初始化channelHandler,设置连?服务端的 Client 的 SocketChannel 的处理器
                .childHandler(new ChannelInitializer<Channel>() 
            //我们再来设置下相应的过滤条件。 这?需要继承Netty中ChannelInitializer 类,
            //然后重写 initChannel 该?法,进?添加相应的设置,传输协议设置,以及相应的业务实现类
                    @Override
                    protected void initChannel(Channel ch) throws Exception 
                        //配置pipeline相关属性
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
                        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
                        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
                        // 相关处理 Handler
                        pipeline.addLast(new TcpServerHandler());
                    
                );
        return serverBootstrap;
    

    protected static void shutdown()
        workGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
    

    public static void main(String[] args) throws Exception 
        System.out.println("启动Server...");
        NettyServer.start();
    

  服务相关的设置的代码写完之后,我们再来编写主要的业务代码。 使?Netty编写 [业务层 ]的代码,我们需要继承 ChannelInboundHandlerAdapter 或 SimpleChannelInboundHandler 类,在这?说下它们两的区别吧。

  继承 SimpleChannelInboundHandler 类之后,会在接收到数据后会?动 release 掉数据占?的 Bytebuffer 资源。并且继承该类需要指定数据格式。

  ?继承ChannelInboundHandlerAdapter 则不会?动释放,需要?动调?ReferenceCountUtil.release() 等?法进?释放。继承该类不需要指定数据格式。 所以在这?,个?推荐服务端继承 ChannelInboundHandlerAdapter ,?动进?释放,防?数据未处理完就?动释放了。?且服务端可能有多个客户端进?连接,并且每?个客户端请求的数据格式都不?致,这时便可以进?相应的处理。

  客户端根据情况可以继承 SimpleChannelInboundHandler 类。好处是直接指定好传输的数据格式,就不需要再进?格式的转换了。

  TcpServerHandler :

public class TcpServerHandler extends ChannelInboundHandlerAdapter 
    //建?连接时,发送?条庆祝消息
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception 
        System.out.println("chanelActive>>>>>>>");
    
    //业务逻辑处理
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception 
        System.out.println("server receive message:" + msg);
        ctx.channel().writeAndFlush("accept message "+ msg);
        ctx.close();
    
    //异常相关处理
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception 

        System.out.println("get server exception :"+cause.getMessage());
    

  客户端:客户端过滤其这块基本和服务端?致。不过需要注意的是,传输协议、编码和解码应该?致.

public class NettyClient implements Runnable 

    @Override
    public void run() 
        EventLoopGroup group = new NioEventLoopGroup();
        try 
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group);
            bootstrap.channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() 
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception 
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
                            pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
                            pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
                            pipeline.addLast("handler", new MyClient());
                        
                    );
            for (int i=0;i<10;i++)
                ChannelFuture f = bootstrap.connect("127.0.0.1",6666).sync();
                f.channel().writeAndFlush("hello service !" + Thread.currentThread().getName()+ ":---->"+i);
                f.channel().closeFuture().sync();
            
        catch (Exception e)
            e.printStackTrace();
        finally 
            group.shutdownGracefully();
        

    

    public static void main(String[] args) 
        for (int i = 0;i < 3 ;i++ )
            new Thread(new NettyClient(),">>> this thread "+i).start();
        
    

  MyClient :这?有个注解, 该注解 Sharable 主要是为了多个handler可以被多个channel安全地共享,也就是保证线程安全。

 

public class MyClient extends ChannelInboundHandlerAdapter 
    //@Sharable
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception 
        System.out.println("client receieve message: "+msg);
    

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception 
        System.out.println("get client exception :"+cause.getMessage());
    

 

  启动服务器,客户端即可看到演示效果。

Netty线程模型:

  了解了Netty 基础服务的构建,我们对Netty服务有了一定的认识,最后来一张线程模型图(Reactor主从多线程模型):

技术图片

以上是关于Netty核心组件及基础服务构建的主要内容,如果未能解决你的问题,请参考以下文章

Day472.Netty 核心模块组件 -netty

Netty学习笔记:Netty核心模块组件

网络I/o编程模型14 netty的核心组件

Day472&473&474.Netty 核心模块组件 -netty

Java面试基础知识,java语言编程入门

Netty核心技术及源码剖析-Netty入站与出站机制