【Netty】专栏总集篇

shadowLogo

前言:

在之前的博文中,本人讲解了什么是 NIO,并介绍了 Java 操作 NIO核心API 的使用
(NIO 博文:《一文彻底理解什么是 NIO》)

但是,在我们使用的过程中,我们也能发现:NIO 的 类库 和 API 繁杂, 使用麻烦:

需要熟练掌握 Selector、 ServerSocketChannel、 SocketChannel、 ByteBuffer等 API 的调用

并且 开发工作量 和 难度 都非常大,例如:

客户端面临断线重连、 网络闪断、心跳处理、半包读写、 网络拥塞 和 异常流的处理 等等

因此,几个前辈 根据 NIO核心API,加以自己的 理解判断 以及 天马行空的 设计思路,设计出了一款 基于 NIO 实现的、既 传输效率高,又 操作简便网络通信框架 —— Netty

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

那么,在本专栏中,本人就来介绍 Netty使用实现原理


基本概念:

图标:

logo


介绍:

Netty 是由JBOSS提供的一个Java开源框架
Netty 提供 异步的基于事件驱动网络应用程序框架
Netty 拥有 高性能吞吐量更高延迟更低减少资源消耗最小化不必要的内存复制 等优点。
Netty 现在都在用的是4.x,5.x版本已经废弃,Netty 4.x 需要JDK 6以上版本支持


应用场景:

  • 互联网行业

在分布式系统中,各个节点之间需要远程服务调用,高性能的 RPC 框架必不可少,
Netty 作为异步高性能的通信框架,往往作为基础通信组件被这些 RPC 框架使用


典型应用

  • 阿里分布式服务框架 Dubbo 的 RPC 框架使用,Dubbo 协议进行节点间通信,
    Dubbo 协议默认使用 Netty 作为基础通信组件,用于实现各进程节点之间的内部通信
  • Rocketmq底层也是用的Netty作为基础通信组件
  • 游戏行业

无论是手游服务端还是大型的网络游戏,Java 语言得到了越来越广泛的应用
Netty 作为高性能的基础通信组件,它本身提供了 TCP/UDPHTTP 协议栈

  • 大数据领域

经典的 Hadoop 的高性能通信和序列化组件 Avro 的 RPC 框架,
默认采用 Netty 进行跨界点通信,它的 Netty Service 基于 Netty 框架二次封装实现


线程模型:

model

  1. Netty 抽象出 两组线程池 —— BossGroupWorkerGroup
组件 功能
BossGroup 接收 客户端的连接
WorkerGroup 处理 网络数据的读写
  1. BossGroupWorkerGroup 的类型都是 NioEventLoopGroup
  2. NioEventLoopGroup 相当于一个 事件循环线程组
    这个组中含有 多个 事件循环线程 , 每一个事件循环线程 都是 NioEventLoop
  3. 每个 NioEventLoop 都有一个 selector,用于监听 注册在其上的socketChannel 的网络通讯
  4. 每个Boss 的 NioEventLoop线程,内部循环执行的步骤有 3 步:
  • 处理accept事件 , 与client 建立连接 , 生成 NioSocketChannel
  • 将 NioSocketChannel 注册到 某个worker 的 NIOEventLoop 的 selector
  • 处理 任务队列的任务,即:runAllTasks
  1. 每个worker 的 NIOEventLoop线程,循环执行的步骤:
  • 轮询注册到自己selector上的所有NioSocketChannel 的read、write事件
  • 处理 I/O 事件, 即read , write 事件, 在 对应的NioSocketChannel 处理业务
  • runAllTasks 处理 任务队列TaskQueue 中的任务 ,
    一些耗时的业务处理一般可以放入TaskQueue中慢慢处理,这样不影响数据在 pipeline 中的流动处理
  1. 每个worker 的 NIOEventLoop处理NioSocketChannel业务时,会使用 pipeline (管道),
    管道中维护了很多 handler 处理器用来处理 channel 中的数据

接下来,本人就来讲解下 Netty 的使用:

首先,是 NettyMaven依赖

Maven依赖:

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.35.Final</version>
</dependency>

接下来,本人来展示下 Netty基本使用

基本使用:

首先是 服务端 的代码:

服务端:

package edu.youzg.demo;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.CharsetUtil;

/**
 * @Author: Youzg
 * @CreateTime: 2021-05-02 19:11
 * @Description: 带你深究Java的本质!
 */
public class NettyServerDemo {

    public static void main(String[] args) {
        /*
            创建 boss、worker 线程组:
                boss线程组 处理 连接请求
                worker线程组 处理 客户端业务
            (默认线程池大小 为 CPU核数的2倍)
         */
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            // 创建 服务端启动对象
            ServerBootstrap bootstrap = new ServerBootstrap();

            // 配置 启动参数
            bootstrap.group(bossGroup, workerGroup) // 配置 接受连接、处理事件 的 事件循环线程组
                    .channel(NioServerSocketChannel.class)  // 设置 服务端的通道 为 NioServerSocketChannel类型(TCP连接)
                    // 初始化 服务器连接队列 的大小,服务端处理客户端连接请求 是 顺序处理的,所以 同一时间 只能处理 一个客户端连接。
                    // 多个客户端 同时来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChannelInitializer<SocketChannel>() { // 设置 通道初始化对象
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            // 为 workerGroup 的 SocketChannel 设置 处理器
                            socketChannel.pipeline().addLast(new NettyServerHandler());
                        }
                    });
            System.out.println("Netty Server start successfully!");

            /*
                绑定 9000端口
               并
                等待 异步操作 执行完毕
             */
            ChannelFuture channelFuture = bootstrap.bind(9000).sync();

            // 为 channelFuture 注册”监听器“
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    if (channelFuture.isSuccess()) {
                        System.out.println("bind 9000 port successfully!");
                    } else {
                        System.out.println("bind 9000 port failure!");
                    }
                }
            });

            /*
                对 通道 关闭监听
               并
                等待 异步操作 执行完毕
             */
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            /*
                优雅停机
             */
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }

    /**
     * 自定义 服务端 入站消息处理器
     */
    static class NettyServerHandler extends ChannelInboundHandlerAdapter {

        /**
         * 读取客户端发送的数据
         *
         * @param ctx 上下文对象, 含有通道channel,管道pipeline
         * @param msg 就是客户端发送的数据
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println("服务器读取线程 " + Thread.currentThread().getName());
            //Channel channel = ctx.channel();
            //ChannelPipeline pipeline = ctx.pipeline(); //本质是一个双向链接, 出站入站
            //将 msg 转成一个 ByteBuf,类似NIO 的 ByteBuffer
            ByteBuf buf = (ByteBuf) msg;
            System.out.println("客户端发送消息是:" + buf.toString(CharsetUtil.UTF_8));
        }

        /**
         * 数据读取完毕处理方法
         *
         * @param ctx 上下文对象, 含有通道channel,管道pipeline
         */
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            ByteBuf buf = Unpooled.copiedBuffer("Hello Client, I am Server", CharsetUtil.UTF_8);
            ctx.writeAndFlush(buf);
        }

        /**
         * 处理异常, 一般是需要关闭通道
         *
         * @param ctx   上下文对象, 含有通道channel,管道pipeline
         * @param cause 异常信息
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }

    }

}

接下来,是 客户端 的代码:

客户端:

package edu.youzg.demo;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.CharsetUtil;

/**
 * @Author: Youzg
 * @CreateTime: 2021-05-02 21:18
 * @Description: 带你深究Java的本质!
 */
public class NettyClientDemo {

    public static void main(String[] args) {
        // 创建 客户端 事件轮询线程组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            // 创建 客户端启动对象
            Bootstrap bootstrap = new Bootstrap();
            // 配置 启动参数
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)  // 设置 服务端的通道 为 NioServerSocketChannel类型(TCP连接)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new NettyClientHandler());
                        }
                    });
            System.out.println("Netty Client start successfully!");
            /*
                连接 服务器
               并
                等待 异步操作 执行完毕
             */
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9000).sync();

            // 为 channelFuture 注册”监听器“
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    if (channelFuture.isSuccess()) {
                        System.out.println("connect server successfully!");
                    } else {
                        System.out.println("connect server failure!");
                    }
                }
            });

            /*
                对 通道 关闭监听
               并
                等待 异步操作 执行完毕
             */
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 优雅停机
            group.shutdownGracefully();
        }
    }

    static class NettyClientHandler extends ChannelInboundHandlerAdapter {

        /**
         * 当客户端连接服务器完成就会触发该方法
         *
         * @param ctx 上下文对象, 含有通道channel,管道pipeline
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            ByteBuf buf = Unpooled.copiedBuffer("Hello Server, I am Client", CharsetUtil.UTF_8);
            ctx.writeAndFlush(buf);
        }

        /**
         * 当通道有读取事件时会触发,即服务端发送数据给客户端
         *
         * @param ctx 上下文对象, 含有通道channel,管道pipeline
         * @param msg 服务端 发送的数据
         */
        @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();
        }

    }


}

接下来,本人来展示下 运行结果

运行结果:

首先是 服务端 的显示结果:
server
接下来是 客户端 的显示结果:
client

可以看到:

双端 通信都成功了!


在了解了 基本使用 之后,本人就来讲解下 Netty模块组件 及其 作用

模块组件:

请观看本人博文 —— 《【Netty】模块组件 详解》


生命周期:

请观看本人博文 —— 《【Netty】生命周期 详解》


接下来,本人来讲解下 Netty编/解码

编/解码:

请观看本人博文 —— 《【Netty】编/解码 详解》


接下来,本人来讲解下 Netty粘包拆包

粘包拆包:

请观看本人博文 —— 《【Netty】粘包/拆包 详解》


接下来,本人来讲解下 Netty心跳机制

心跳机制:

请观看本人博文 —— 《【Netty】心跳机制 详解》


接下来,本人来讲解下 Netty断线自动重连

断线自动重连:

请观看本人博文 —— 《【Netty】断线重连 详解》


接下来,本人来讲解下 Netty核心源码

由于 服务端客户端核心源码 类似,
因此,本人就来通过讲解 服务端 的 源码,来带同学们学习 Netty架构思想

核心源码:

请观看本人博文 —— 《【源码解析】Netty 服务端 详解》


最后,本人来扩展一个 在 开发 中,十分重要的知识点 —— Selector空轮询 的 解决

扩展 —— Selector空轮询 的 解决:

请观看本人博文 —— 《【Netty】Selector空轮询 解决 详解》


那么,看到这里,本专栏的博文,就讲解结束了!

NettyJava届 是十分有名的!
可以这样讲:

NettyJava中间件 的 地位
相当于
Springweb开发届 的 地位

所以,我们如果想在 Java 上做 长期学习 以及 深造 的话,本人还是建议在 Netty 上多下些功夫!

秋招在即,希望和本人一样还在为了梦想拼搏的同学能够圆梦!
加油

posted @ 2021-05-08 20:38  在下右转,有何贵干  阅读(168)  评论(0编辑  收藏  举报