一、Netty的概述

学习Netty,我们首先得知道什么是Netty
Netty:Netty是Java的一个开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速的开发高性能、高可靠性的网络服务器和客户端程序。也就是说Netty是一个基于NIO客户端、服务端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。

二、为什么要学习netty

既然Netty是一个网络应用程序开发框架,那为什么我们不用NIO呢(况且Netty也还是基于NIO开发的),首先我们得了解NIO,这里就不多做叙述,如果想了解一下NIO,可以看我之前写的NIO文章NIO主要的问题是

  • 首先NIO的类库以及API都是比较复杂,繁琐,学习成本是比较高的,学习NIO你得了解:Channel(通道)、Buffer(缓冲区)、Selector(选择器)NIO的三大核心部分等。
  • 还需要熟悉java的多线程编程,因为NIO编程涉及到Reactor模式,必须对多线程和网络编程非常熟悉,才能写出高质量的NIO程序
  • 还有epoll bug问题,导致Selector空轮询,导致CPU 100%。直到JDK1.7都没有根本性解决。

Netty的优点
Netty对JDK自带的NIO的API进行封装,解决了上述问题

  • 设计优雅:适用于各种传输类型的统一API;基于灵活可扩展的事件模型;以及高度可定制的线程模型。
  • 使用方便:有详细的用户指南和示例。
  • 高性能、吞吐量,延迟更低
  • 社区更加活跃,在不断更新

三、Netty的线程模型

不同的线程模式,对程序的性能有很大的影响,在研究Netty的线程模式之前,我们先了解存在的各种线程模式,做一下比较。
目前存在的线程模型有

  • 传统的I/O服务模型
  • Reactor模式

根据Reactor的数量和处理资源池线程的数量不同,又可以分为3种实现

  • 单Reactor单线程
  • 单Reactor多线程
  • 主从Reactor多线程

传统的I/O服务模型

netty框架在springboot项目中的使用原理 netty框架入门_客户端


采用阻塞IO模式获取数据,每一个连接都要独立的线程完成数据传输,业务处理、数据返回。
产生的问题:当并发数很大时,会创建大量的线程,占用很大的系统资源。
连接创建后,如果当前线程没有数据读取,该线程会一直阻塞在read操作,造成资源浪费。

Reactor模式

针对传统阻塞I/O服务模型的弊端,解决方法

(1)基于I/O复用模型:多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象等待,无需阻塞等待所有连接。当某一个有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理。

(2)基于线程池复用线程资源:不必为每一个连接创建线程,将连接弯沉后的业务处理任务分配给线程进行处理,一个线程可以处理多个业务连接。

netty框架在springboot项目中的使用原理 netty框架入门_客户端_02


(1)Reactor模式,通过一个或多个输入同时传递给服务器的模式(基于事件驱动)

(2)服务器程序处理传入的多个请求,并将它们分派到相应的处理线程,因此Reactor模式又叫Dispatcher模式

(3)Reactor使用IO复用监听事件,收到事件后,分发给某个线程,这也是服务器高并发处理的关键

Reactor模式中的核心组成
(1)Reactor:在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对IO事件做出反应
(2)Handlers:处理程序执行I/O 事件要完成的实际事件

netty框架在springboot项目中的使用原理 netty框架入门_客户端_03


(1)Select是前面IO复用模型介绍的标准网络编程API,可以实现应用程序通过一个阻塞对象监听多路连接请求

(2)Reactor对象通过Select监控客户端请求事件,收到事件后通过Dispatch进行分发

(3)如果建立连接请求事件,则由Acceptor通过Accept处理连接请求,然后创建一个Handler对象处理连接完成后的后续业务处理

(4)如果不是建立连接事件,则Reactor会分发调用连接对应的Handler来响应

(5)Handler会完成Read —>业务处理 -->send的完成流程

Netty的工作原理

netty框架在springboot项目中的使用原理 netty框架入门_客户端_04


(1)Netty抽象出两组线程池BossGroup专门负责接收客户端的连接,WorkGroup专门负责网络的读写

(2)BossGroup和WorkGroup类型都是NioEventLoopGroup

(3)NIOEventLoopGroup相当于一个事件循环组,这个组中含有多个事件循环,每一个事件循环是NioEventLoop

(4)NioEventLoop表示一个不断循环的执行处理任务的线程,每个NioEventLoop都有一个selector,用于监听绑定在其上的socket的网络通讯

(5)NioEventLoopGroup可以有多个线程,即可以含有多个NioEventLoop

(6)每个BossNioEventLoop循环执行的步骤有3步:

轮询accept事件

处理accept事件,与client建立连接,生成NioSocketChannel,并将其注册到某个WorkNioEventLoop上的select

处理任务队列的任务,即runAllTasks

(7)每一个WorkNIOEventLoop循环执行的步骤

轮询read、write事件

处理I/O事件,即read、write事件,在对应NIOSocketChannel处理

处理任务队列的任务,即runAllTasks

(8)每个WorkerNIOEventLoop处理业务时,会使用pipeline,pipeline中包含channel,即通过pipeline可以获取到对应管道,管道中维护了很多处理器

四、Netty实战演示

首先引入Netty的jar包

netty框架在springboot项目中的使用原理 netty框架入门_netty_05

创建服务端的启动类

public class NettyServer {
    public static void main(String[] args) throws Exception {
        //bossGroup只处理连接请求,workGroup和客户端业务处理
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workGroup = new NioEventLoopGroup();

        try {
			//创建服务端的启动对象,设置参数
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workGroup)//设置两个线程组
                    .channel(NioServerSocketChannel.class)//NioServerSocketChannel服务器通道
                    .option(ChannelOption.SO_BACKLOG, 128)//线程队列得到的连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true)//设置保持活动连接状态
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        //创建通道测试对象
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                        //给pipeline管道设置处理器
                            socketChannel.pipeline().addLast(new NettyServerHandler());
                        }
                    });
            System.out.println("服务器 is ready...");

            //绑定端口并同步,生成一个ChannelFuture 对象
            //启动服务器,绑定端口
            ChannelFuture cf = bootstrap.bind(6668).sync();
            //对关闭通道监听
            cf.channel().closeFuture().sync();

        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}

创建服务端的处理器

public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    //ChannelHandlerContext ctx:上下文,含有管道pipeline,通道channel,地址
    //msg客户端发送的数据
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        super.channelRead(ctx, msg);

        System.out.println("服务器读取线程"+Thread.currentThread().getName());
        System.out.println("server ctx="+ctx);
        Channel channel = ctx.channel();
        ChannelPipeline pipeline = ctx.pipeline();

		//获取客户端发送的消息
        ByteBuf buffer = (ByteBuf) msg;
        System.out.println("客户端消息是:"+buffer.toString(CharsetUtil.UTF_8
        ));
        System.out.println("客户端地址:"+channel.remoteAddress());

    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    //发送消息给客户端
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello yy",CharsetUtil.UTF_8));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    //异常关闭
        ctx.close();
    }
}

创建客户端启动类

public class NettyClient {
    public static void main(String[] args) throws Exception{
        //客户端需要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //创建客户端启动对象
            Bootstrap bootstrap = new Bootstrap();
            //设置参数
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>(){

                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new NettyClientHandler());//加入处理器
                        }
                    });
            System.out.println("客户端 ok...");
            //启动客户端连接服务器
            //连接服务端,关于ChannelFuture分析,涉及netty异步模型
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1",6668).sync();
            //给关闭通道进行监听
            channelFuture.channel().closeFuture().sync();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            group.shutdownGracefully();
        }
    }
}

创建客户端处理器

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("client"+ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,server", CharsetUtil.UTF_8));

    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    //接收服务端发送的消息
        ByteBuf buf = (ByteBuf)msg;
        System.out.println("服务器回复的消息:"+buf.toString(CharsetUtil.UTF_8));
        System.out.println("服务器的地址:"+ctx.channel().remoteAddress());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}