【Netty系列】Netty高性能架构设计以及光速入门

在这里插入图片描述

Netty专栏欢迎大家订阅



一、什么是Netty?

 关于这个问题,我贴一段官网的阐述:

Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients.

 大概意思是:

Netty 是一个 NIO 客户端服务器框架,使用它可以快速简单地开发网络应用程序,比如服务器和客户端的协议。

 具体来说,Netty就是对于Java NIO 的封装。NIO是Java 1.4 后引入的基于事件模型的非阻塞IO框架,在NIO之前,对于数据的处理都是基于BIO(Blocking IO)的,闻其名知其意,BIO是以阻塞的形式来对数据进行处理的,虽然这种方式处理起来比较简单,但是由于其阻塞特性会涉及到线程的上下文切换操作,导致 BIO在高并发场景下略显吃力。

 相对的,NIO能够较好的应对这些场景。但是Java推出的NIO类库和API繁杂,使用起来比较麻烦且容易出现Bug。因此出现了一系列解决NIO问题的框架,Netty就是其中最著名的一个。


Netty有哪些特点?

  • 基于事件机制(Pipeline-Hhandler)达成关注点分离(消息编解码、协议编解码、业务处理);
  • 可定制的线程处理模型、单线程、多线程池等;
  • 屏蔽NIO本身的Bug;
  • 性能上的优化;
  • 相较于NIO接口功能更丰富;
  • 对外提供统一的接口,底层支持BIO与NIO两种方式,可自由切换。

二、Netty总体架构


 首先从官网摘一张结构图:

在这里插入图片描述

 总体来说,Netty分为两大模块:核心模块服务模块

2.1 核心模块

 核心模块主要提供Netty的一些基础接类和底层接口,由三部分组成:

  1. Zero-Copy-Capable Rich Byte Buffer(零拷贝缓冲区):用来提升性能、减少资源占用。
  2. Universal Communication API(统一通信API):Netty为同步和异步IO提供了统一的编程接口。
  3. Extensible Event Model(易扩展的事件模型)
2.2 服务模块

 由于Netty的核心是IO,因此其服务模块与IO操作息息相关,主要有:

  1. 网络接口数据处理相关服务:如报文的粘包、拆包处理,数据的加密、解密等。
  2. 各网络层协议实现服务:包括传输层和应用层相关网络协议的实现
  3. 文件处理相关服务

三、Netty处理架构


 介绍完Netty总体架构,再来瞧瞧Netty的处理架构,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Netty处理架构很清晰,分为三层:

  1. 底层的IO复用层(Reactor):负责实现多路复用。
  2. 中间层的通用数据处理层(PipeLine):主要作用是对传输层的数据在进出口进行拦截并处理,比如粘包处理、编解码等操作。
  3. 顶层的应用实现层:开发者使用Netty的时候基本是在这一层进行操作,同时Netty本身已经在这一层提供了一些常用的实现,如HTTP协议FTP协议等。

整体流程:

 数据从网络传输到IO复用层,IO复用层收到数据后将数据传递给通用数据处理层进行处理,这一层会通过一系列的处理 Handler 以及应用服务器对数据进行处理,然后返回给IO复用层,再通过它传回网络。


四、Netty线程模型介绍

 不同的线程模型对程序的性能有很大的影响,目前存在的线程模型有传统阻塞I/O服务模型 Reactor 模式。根据 Reactor 的数量和处理资源池线程的数量不同又可以分为三种典型实现,分别是单 Reactor 单线程单 Reactor 多线程主从 Reactor 多线程。Netty 主要基于主从 Reactor 多线程模型做了一定的改进,其中主从 Reactor 多线程模型有多个 Reactor 。下图为主从 Reactor 多线程模型图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其运行流程如下:

  • 首先Reactor主线程MainReactor对象通过select监听连接事件,收到事件后,通过Acceptor处理连接事件。
  • Acceptor处理连接事件后,MainReactor将连接分配给SubReactor,然后SubReactor将连接加入到连接队列进行监听,并创建handler进行各种事件处理。
  • 当有新的事件发生时,SubReactor就会调用对应的handler来进行处理。handler通过read读取数据,分发给后面的worker线程处理。
  • worker线程池分配独立的worker线程进行业务处理,并返回结果。
  • handler收到响应结果后,再通过send将结果返回给client

优点: 父线程与子线程的数据交互简单、职责明确,父线程只负责接受新连接,子线程完成后续的业务处理。


Netty工作原理示意图如下:

在这里插入图片描述

 Netty 抽象出了两组线程池BossGroupWorkerGroup,其中BossGroup专门负责接受客户端的连接,WorkerGroup专门负责网络的读写。BossGroupWorkerGroup的类型都是NioEventLoopGroupNioEventLoopGroup相当于一个事件循环组,这个组种含有多个事件循环,每一个事件循环是一个NioEventLoopNioEeventLoop表示一个不断循环执行处理任务的线程,可以有多个线程,也就是说可以含有多个NioEventLoop,每个NioEventLoop都有一个Selector,用于监听绑定在其上的socket的网络通讯。

 每个BossNioEventLoop循环执行的步骤有3步:①轮询accept事件 ②处理accept事件,与client建立连接,生成 NioSocketChannel,并将其注册到某个WorkerNioEventLoop上的Selector ③处理任务队列的任务,即runAllTasks

 每个WorkerNioEventLoop循环执行的步骤也有3步:①轮询read、write事件 ②处理I/O事件,即read、write事件,在对应NioSocketChannel处理 ③处理任务队列的任务,即runAllTasks

 每个WorkerNioEventLoop处理业务时,会使用pipeline(管道),pipeline中包含了channel,也就是说通过pipeline可以获取到对应通道,管道中维护了很多的处理器。


五、Netty光速入门一:TCP服务

 接下来使用Netty实现TCP服务,首先Netty服务器监听9999端口,客户端发送消息给服务器 "hello, world!" 。服务器回复消息 “hello,please stop the war!” 给客户端。

  1. 导入Netty依赖:
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.18.Final</version>
</dependency>
  1. NettyServer.java
package org.lwz.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class NettyServer {
    public static void main(String[] args) {
        /**
         * 流程:
         * 1.创建两个线程组 bossGroup 和 workerGroup
         * 2.bossGroup只负责连接请求的处理,workerGroup完成客户端业务处理
         * 3.两个线程组都置为无线循环
         * 4.bossGroup 和 workerGroup 含有的子线程(NioEventLoop)的个数默认为 实际cpu核数*2
         */
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);  // 线程数1
        EventLoopGroup workerGroup = new NioEventLoopGroup();  // 线程数8
        try{
            // 创建服务端的启动对象
            ServerBootstrap bootStrap = new ServerBootstrap();
            // 配置相应参数
            bootStrap.group(bossGroup, workerGroup)  // 设置两个线程组
                    .channel(NioServerSocketChannel.class)  // 使用NioSocketChannel
                    .option(ChannelOption.SO_BACKLOG, 128)  // 设置线程队列的连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true)  // 设置保持活动连接状态
                    .childHandler(new ChannelInitializer<SocketChannel>() {  // 创建通道初始化对象
                        // 给pipeline设置处理器
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            System.out.println("客户端socketChannel hashCode = " + socketChannel.hashCode());
                            socketChannel.pipeline().addLast(new NettyServerHandler());
                        }
                    });  // 给workerGroup的EventLoop对应的管道配置处理器
            System.out.println("!!服务器已准备好!!");

            // 绑定端口并且同步,生成一个ChannelFuture对象
            // 启动服务器(并绑定端口)
            ChannelFuture channelFuture = bootStrap.bind(9999).sync();
            // 为channelFuture注册监听器,监控关心的事件是否发生
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    if(channelFuture.isSuccess()){
                        System.out.println("监听端口 9999 成功");
                    } else {
                        System.out.println("监听端口 9999 失败");
                    }
                }
            });
            // 对关闭通道进行监听
            channelFuture.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
  1. NettyServerHandler.java
package org.lwz.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.util.CharsetUtil;

public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 1. ChannelHandlerContext ctx:上下文对象,含有管道pipeline,通道channel,地址
     * 2. Object msg:客户端发来的数据,默认Object
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("服务器读取线程 " + Thread.currentThread().getName() + " channel = " + ctx.channel());
        System.out.println("server ctx = " + ctx);
        // 获取channel
        Channel channel = ctx.channel();
        ChannelPipeline pipeline = ctx.pipeline();
        // 将 msg 转成 ByteBuf
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("客户端发来消息:" + buf.toString(CharsetUtil.UTF_8));
        System.out.println("客户端地址:" + channel.remoteAddress());
    }

    // 数据读取完毕
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // writeAndFlush 是 write + flush 操作, 将数据写入到缓存并刷新
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,please stop the war!", CharsetUtil.UTF_8));
    }

    // 有异常时回调到这里,一般是需要关闭通道
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}
  1. NettyClient.java
package org.lwz.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class NettyClient {
    public static void main(String[] args) {
        // 客户端需要一个事件循环组
        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());  // 加入客户端的handler
                        }
                    });
            System.out.println("!!客户端已准备好!!");
            // 启动客户端去连接服务端
            ChannelFuture channelFuture = bootStrap.connect("127.0.0.1", 9999).sync();
            // 监听关闭通道
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            group.shutdownGracefully();
        }
    }
}
  1. NettyClientHandler.java
package org.lwz.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    // 当通道就绪时就会触发该方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端: " + ctx);
        // 向服务端发送数据
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,world!", 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();
    }
}
  1. 启动NettyServer.java以及NettyClient.java,得到测试结果如下:
    在这里插入图片描述
    在这里插入图片描述

  • 107
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 29
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 29
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值