关于java:Netty-源码分析系列一Netty-概述

6次阅读

共计 9215 个字符,预计需要花费 24 分钟才能阅读完成。

前言

对于 Netty 的学习,最近看了不少无关视频和书籍,也播种不少,心愿把我晓得的分享给你们,一起加油,一起成长。后面咱们对 Java IOBIONIOAIO进行了剖析,相干文章链接如下:

深入分析 Java IO(一)概述

深入分析 Java IO(二)BIO

深入分析 Java IO(三)NIO

深入分析 Java IO(四)AIO

本篇文章咱们就开始对 Netty来进行深入分析,首先咱们来理解一下 JAVA NIOAIO的不足之处。

Java 原生 API 之痛

尽管 JAVA NIOJAVA AIO 框架提供了多路复用 IO/ 异步 IO 的反对,然而并没有提供下层“信息格式”的良好封装。用这些 API 实现一款真正的网络应用则并非易事。

JAVA NIOJAVA AIO并没有提供断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异样码流等的解决,这些都须要开发者本人来补齐相干的工作。

AIO在实践中,并没有比 NIO 更好。AIO在不同的平台有不同的实现,windows 零碎下应用的是一种异步 IO 技术:IOCP;Linux 下因为没有这种异步 IO 技术,所以应用的是epoll 对异步 IO 进行模仿。所以 AIO 在 Linux 下的性能并不现实。AIO 也没有提供对 UDP 的反对。

综上,在理论的大型互联网我的项目中,Java 原生的 API 利用并不宽泛,取而代之的是一款第三方 Java 框架,这就是Netty

Netty 的劣势

Netty 提供 异步的、事件驱动的网络应用程序框架和工具,用以疾速开发高性能、高可靠性的网络服务器和客户端程序。

非阻塞 I/O

Netty 是基于 Java NIO API 实现的网络应用框架,应用它能够疾速简略的开发网络应用程序,如服务器和客户端程序。Netty 大大简化了网络程序开发的过程,如 TCP 和 UDP 的 Socket 服务的开发。

因为是基于 NIO 的 API,因而,Netty 能够提供非阻塞的 I/O操作,极大的晋升了性能。同时,Netty 外部封装了 Java NIO API 的复杂性,并提供了线程池的解决,使得开发 NIO 的利用变得极其简略。

丰盛的协定

Netty 提供了简略、易用的 API , 但这并不意味着应用程序会有难保护和性能低的问题。Netty 是一个精心设计的框架,它从许多协定的实现中排汇了很多的教训,如 FTP、SMTP、HTTP、许多二进制和基于文本的传统协定。

Netty 反对丰盛的网络协议,如 TCPUDPHTTPHTTP/2WebSocketSSL/TLS 等,这些协定实现开箱即用,因而,Netty 开发者可能在不失灵便的前提下来实现开发的繁难性、高性能和稳定性。

异步和事件驱动

Netty 是异步事件驱动的框架,该框架体现为所有的 I/O 操作都是异步的,所有的 I/O 调用会立刻返回,并不保障调用胜利与否,然而调用会返回 ChannelFuture。Netty 会通过 ChannelFuture 告诉调用是胜利了还是失败了,抑或是勾销了。

同时,Netty 是基于事件驱动的,调用者并不能立刻取得后果,而是通过事件监听机制,用户能够不便地被动获取或者通过告诉机制取得 I/O 操作的后果。

Future 对象刚刚创立时,处于非实现状态,调用者能够通过返回的 ChannelFuture 来获取操作执行的状态,再通过注册监听函数来执行实现后的操作,常见有如下操作:

  • 通过 isDone 办法来判断以后操作是否实现。
  • 通过 isSuccess 办法来判断已实现的以后操作是否胜利。
  • 通过 getCause 办法来获取已实现的以后操作失败的起因。
  • 通过 isCancelled 办法来判断已实现的以后操作是否被勾销。
  • 通过 addListener 办法来注册监听器,当操作已实现 (isDone 办法返回实现),将会告诉指定的监听器;如果 future 对象已实现,则了解告诉指定的监听器。

例如:上面的代码中绑定端口是异步操作,当绑定操作解决完,将会调用相应的监听器解决逻辑。

serverBootstrap.bind(port).addListener(future -> {if(future.isSuccess()){System.out.println("端口绑定胜利!");
    }else {System.out.println("端口绑定失败!");
    }
});

相比传统的阻塞 I/O,Netty 异步解决的益处是不会造成线程阻塞,线程在 I/O操作期间能够执行其余的程序,在高并发情景下会更稳固并领有更高的吞吐量。

精心设计的 API

Netty 从开始就为用户提供了体验最好的 API 及实现设计。

例如,在用户数较小的时候可能会抉择传统的阻塞 API,毕竟与 Java NIO 相比应用阻塞 API 将会更加容易一些。然而,当业务量呈指数增长并且服务器须要同时解决成千上万的客户连贯,便会遇到问题。这种状况下可能会尝试应用 Java NIO,然而简单的 NIO Selector 编程接口又会消耗大量的工夫并最终会妨碍疾速开发。

Netty 提供了一个叫作 channel的对立的异步 I/O 编程接口,这个编程接口形象了所有点对点的通信操作。也就是说,如果利用是基于 Netty 的某一种传输实现,那么同样的,利用也能够运行在 Netty 的另一种传输实现上。Channel常见的子接口有:

丰盛的缓冲实现

Netty 应用自建的缓存 API,而不是应用 Java NIO 的 ByteBuffer 来示意一个间断的字节序列。与 ByteBuffer 相比,这种形式领有显著的劣势。

Netty 应用新的缓冲类型 ByteBuf , 并且被设计为可从底层解决 ByteBuffer 问题,同时还满足日常网络应用开发须要的缓冲类型。

Netty 重要有以下个性:

  • 容许应用自定义的缓冲类型。
  • 复合缓冲类型中内置通明的零拷贝实现。
  • 开箱即用动静缓冲类型,具备像 StringBuffer 一样的动静缓冲能力。
  • 不再须要调用 flip() 办法。
  • 失常状况下具备比 ByteBuffer 更快的响应速度。

高效的网络传输

Java 原生的序列化次要存在以下几个弊病:

  • 无奈跨语言。
  • 序列化后码流太大。
  • 序列化后性能太低。

业界有十分多的框架用于解决上述问题,如 Google ProtobufJBoss MarshallingFacebook Thrift等。针对这些框架,Netty 都提供了相应的包将这些框架集成到利用中。同时,Netty 自身也提供了泛滥的编解码工具,不便开发者应用。开发者能够基于 Netty 来开发高效的网络传输利用,例如:高性能的消息中间件 Apache RocketMQ、高性能 RPC 框架 Apache Dubbo 等。

Netty 外围概念

从上述的架构图能够看出,Netty 次要由三大块组成:

  • 外围组件
  • 传输服务
  • 协定

外围组件

外围组件包含:事件模型、字节缓冲区和通信 API

事件模型

Netty 是基于异步事件驱动的,该框架体现为所有的 I/O 操作都是异步的,调用者并不能立刻取得后果,而是通过事件监听机制,用户能够不便地被动获取或者通过告诉机制取得 I/O 操作的后果。

Netty 将所有的事件依照它们与入站或出站数据流的相关性进行了分类。

可能由入站数据或者相干的状态更改而触发的事件包含以下几项:

  • 连贯已被激活或者连贯失活。
  • 数据读取。
  • 用户事件。
  • 谬误事件。

出站事件是将来将会触发的某个动作的操作后果,包含以下动作:

  • 关上或者敞开到近程节点的连贯。
  • 将数据写到或者冲刷到套接字。

每个事件都能够被散发到 ChannelHandler 类中的某个用户实现的办法。

字节缓冲区

Netty 应用了区别于 Java ByteBuffer 的新的缓冲类型ByteBuf,ByteBuf 提供了丰盛的个性。

通信 API

Netty 的通信 API 都被形象到 Channel 里,以对立的异步 I/O 编程接口来满足所有点对点的通信操作。

传输服务

Netty 内置了一些开箱即用的传输服务。因为并不是它们所有的传输都反对每一种协定,所以必须抉择一个和应用程序所应用的协定相兼容的传输。以下是 Netty 提供的所有的传输。

NIO

io.netty.channel.socket.nio包用于反对 NIO。该包上面的实现是应用 java.nio.channels 包作为根底(基于选择器的形式)。

epoll

io.netty.channel.epoll包用于反对由 JNI 驱动的 epoll 和 非阻塞 IO。

须要留神的是,这个 epoll 传输只能在 Linux 上取得反对。epoll同时提供多种个性,如:SO_REUSEPORT 等,比 NIO 传输更快,而且是齐全非阻塞的。

OIO

io.netty.channel.socket.oio包用于反对应用 java.net 包作为根底的阻塞I/O

本地

io.netty.channel.local包用于反对在 VM 外部通过管道进行通信的本地传输。

内嵌

io.netty.channel.embedded包作为内嵌传输,容许应用 ChannelHandler 而又不须要一个真正的基于网络的传输。

协定反对

Netty 反对丰盛的网络协议,如 TCPUDPHTTPHTTP/2WebSocketSSL/TLS 等,这些协定实现开箱即用,因而,Netty 开发者可能在不失灵便的前提下来实现开发的繁难性、高性能和稳定性。

Netty 简略利用

引入 Maven 依赖

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

服务端的管道处理器

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("server ctx =" + ctx);
        Channel channel = ctx.channel();
        // 将 msg 转成一个 ByteBuf
        //ByteBuf 是 Netty 提供的,不是 NIO 的 ByteBuffer.
        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("公司最近账户没啥钱,再等几天吧!", CharsetUtil.UTF_8));
    }

    // 解决异样, 个别是须要敞开通道
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.close();
    }
}

NettyServerHandler继承自 ChannelInboundHandlerAdapter,这个类实现了ChannelInboundHandler 接口。ChannelInboundHandler提供了许多事件处理的接口办法。

这里笼罩了 channelRead() 事件处理办法。每当从客户端收到新的数据时,这个办法会在收到音讯时被调用。

channelReadComplete()事件处理办法是数据读取结束时被调用,通过调用 ChannelHandlerContextwriteAndFlush()办法,把音讯写入管道,并最终发送给客户端。

exceptionCaught()事件处理办法是,当呈现 Throwable 对象时才会被调用。

服务端主程序

public class NettyServer {public static void main(String[] args) throws Exception {
        // 创立 BossGroup 和 WorkerGroup
        // 阐明
        //1. 创立两个线程组 bossGroup 和 workerGroup
        //2. bossGroup 只是解决连贯申请 , 真正的和客户端业务解决,会交给 workerGroup 实现
        //3. 两个都是有限循环
        //4. bossGroup 和 workerGroup 含有的子线程 (NioEventLoop) 的个数
        //   默认理论 cpu 核数 * 2
        //
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(); //8
        try {
            // 创立服务器端的启动对象,配置参数
            ServerBootstrap bootstrap = new ServerBootstrap();
            // 应用链式编程来进行设置
            bootstrap.group(bossGroup, workerGroup) // 设置两个线程组
                    .channel(NioServerSocketChannel.class) //bossGroup 应用 NioSocketChannel 作为服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列失去连贯个数 option 次要是针对 boss 线程组,.childOption(ChannelOption.SO_KEEPALIVE, true) // 设置放弃流动连贯状态 child 次要是针对 worker 线程组
                    .childHandler(new ChannelInitializer<SocketChannel>() {//workerGroup 应用 SocketChannel 创立一个通道初始化对象                                                                                                                        (匿名对象)
                        // 给 pipeline 设置处理器
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            // 能够应用一个汇合治理 SocketChannel,再推送音讯时,能够将业务退出到各个 channel 对应的 NIOEventLoop 的                                     taskQueue 或者 scheduleTaskQueue
                            ch.pipeline().addLast(new NettyServerHandler());
                        }
                    }); // 给咱们的 workerGroup 的 EventLoop 对应的管道设置处理器

            System.out.println("..... 服务器 is ready...");
            // 绑定一个端口并且同步, 生成了一个 ChannelFuture 对象
            // 启动服务器(并绑定端口)
            ChannelFuture cf = bootstrap.bind(7788).sync();
            // 给 cf 注册监听器,监控咱们关怀的事件
            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {if (cf.isSuccess()) {System.out.println("服务已启动, 端口号为 7788...");
                    } else {System.out.println("服务启动失败...");
                    }
                }
            });
            // 对敞开通道进行监听
            cf.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();}
    }
}

NioEventLoopGroup是用来解决 I/O 操作的多线程事件循环器。Netty 提供了许多不同的 EventLoopGroup 的实现来解决不同的传输。

下面的服务端利用中,有两个 NioEventLoopGroup 被应用。第一个叫作 bossGroup,用来接管进来的连贯。第二个叫作workerGroup,用来解决曾经被接管的连贯,一旦 bossGroup 接管连贯,就会把连贯的信息注册到 workerGroup 上。

ServerBootstrap是一个 NIO 服务的疏导启动类。能够在这个服务中间接应用Channel

  • group办法用于 设置EventLoopGroup
  • 通过 Channel 办法,能够指定新连贯进来的 Channel 类型为 NioServerSocketChannel 类。
  • childHandler用于指定ChannelHandler,也就是后面实现的NettyServerHandler
  • 能够通过 option 设置指定的 Channel 来实现 NioServerSocketChannel 的配置参数。
  • childOption次要设置 SocketChannel 的子 Channel 的选项。
  • bind用于绑定端口启动服务。

客户端管道处理器

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    // 当通道就绪就会触发该办法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println("client ctx =" + ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("老板,工资什么时候发给我啊?", 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();}
}

channelRead办法中将接管到的音讯转化为字符串,不便在管制台上打印进去。

channelRead接管到的音讯类型为 ByteBufByteBuf 提供了转为字符串的不便办法。

客户端主程序

public class NettyClient {public static void main(String[] args) throws Exception {
        // 客户端须要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            // 创立客户端启动对象
            // 留神客户端应用的不是 ServerBootstrap 而是 Bootstrap
            Bootstrap bootstrap = new Bootstrap();
            // 设置相干参数
            bootstrap.group(group) // 设置线程组
                    .channel(NioSocketChannel.class) // 设置客户端通道的实现类(反射)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new NettyClientHandler()); // 退出本人的处理器
                        }
                    });
            System.out.println("客户端 ok..");
            // 启动客户端去连贯服务器端
            // 对于 ChannelFuture 要剖析,波及到 netty 的异步模型
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 7788).sync();
            // 给敞开通道进行监听
            channelFuture.channel().closeFuture().sync();} finally {group.shutdownGracefully();
        }
    }
}

客户端只须要一个 NioEventLoopGroup 就能够了。

测试运行

别离启动服务器 NettyServer 和客户端 NettyClient程序

服务端控制台输入内容:

..... 服务器 is ready...
服务已启动, 端口号为 7788...
server ctx =ChannelHandlerContext(NettyServerHandler#0, [id: 0xa1b2233c, L:/127.0.0.1:7788 - R:/127.0.0.1:63239])
客户端发送音讯是: 老板,工资什么时候发给我啊?客户端地址:/127.0.0.1:63239

客户端控制台输入内容:

客户端 ok..
client ctx =ChannelHandlerContext(NettyClientHandler#0, [id: 0x21d6f98e, L:/127.0.0.1:63239 - R:/127.0.0.1:7788])
服务器回复的音讯: 公司最近账户没啥钱,再等几天吧!服务器的地址:/127.0.0.1:7788

至此,一个简略的基于 Netty 开发的服务端和客户端就实现了。

总结

本篇文章次要解说了 Netty 产生的背景、特点、外围组件及如何疾速开启第一个 Netty 利用。

前面咱们会剖析 Netty 架构设计ChannelChannelHandler、字节缓冲区ByteBuf 线程模型 编解码 疏导程序 等方面的常识。

结尾

我是一个正在被打击还在致力后退的码农。如果文章对你有帮忙,记得点赞、关注哟,谢谢!

正文完
 0