咱们好,我是老三,之前里,咱们评论了Java的三种IO模型,提到了网络通讯结构Netty,它简化和优化了NIO的运用,这期,咱们正式开端走近Netty。

为什么要用Netty?

首要当然是NIO的运用,自身比较复杂,而且还存在一些问题。

除此之外,如果在项目的开发中,要完成安稳的网络通讯,就得考虑网络的闪断、客户端的重复接入、客户端的安全认证、音讯的编解码、半包读写……

玩转Netty,从“Hello World”开始

所以,巧了,恰好有这么一个成熟安稳、功能强大、开箱即用的网络结构摆在咱们面前,相比较Java NIO,Netty愈加出色:

  • 易用性: Netty 在 NIO 基础上进行了更高层次的封装,屏蔽了 NIO 的复杂性,大大降低了开发难度;Netty 供给了许多开箱即用的工具,例如常用的行解码器、长度域解码器等,不需要自己再完成。
  • 安稳性: Netty 愈加牢靠安稳,修正和完善了 JDK NIO 较多已知问题,例如臭名昭著的 select 空转导致 CPU 耗费 100%,TCP 断线重连,keep-alive 检测等问题。
  • 可扩展性: Netty 的的可扩展性做的十分好,比如支持可定制化的线程模型。

咱们有什么理由回绝这么一款优异的网络通讯结构呢?代码怎样写不是写喽!

初识Netty

什么是Netty?

Netty官方是这么界说Netty的:

Netty 是一个异步事情驱动的网络应用程序结构,用于快速开发可保护的高功能协议服务器和客户端。

玩转Netty,从“Hello World”开始

  • Netty是一个开源的、单线程模型的 Java 网络编程结构。

  • Netty根据 NIO ,被广泛应用于各种网络应用程序开发。

  • Netty支持多种协议,包含但不限于 HTTP、WebSocket、TCP、UDP 和 SSL/TLS 协议等。

  • Netty 对错阻塞的,事情驱动的结构。

  • Netty具有高功能、可扩展和易于运用的长处。

Netty的现状?

Netty 由 JBoss 社区开发保护的,它的社区相对比较活泼:

  • github.com/netty/netty…
  • netty.io/:官方网站,供给了比较…

官方目前最新的版别是5.x,,可是很不幸,现已被社区放弃开发保护,归于抛弃版别,最新的安稳版别是4.x 。

一般运用,引荐4.x,Netty 4.x对3.x不做兼容,咱们后续的学习也根据Netty 4.x版别。

谁在用Netty?

作为最盛行的网络通讯结构,大量的公司挑选它作为底层网络通讯结构,包含不限于:

玩转Netty,从“Hello World”开始

咱们可能自己没有直接用过Netty,但其实了解的许多开源中间件,都用到了Netty,比如:

  • 服务管理:Apache Dubbo、gRPC。
  • 大数据:Hbase、Spark、Flink、Storm。
  • 搜索引擎:Elasticsearch。
  • 音讯行列:RocketMQ、ActiveMQ。

用到Netty的优异产品十分多,咱们感兴趣能够看看:netty.io/wiki/relate…

从”Hello World”开端

气氛衬托到这,不写个Demo也过不去,仍是从”Hello World”开端,咱们领会一下Netty的风貌。

  1. 创立一个Maven项目:这个就不必多说了吧

玩转Netty,从“Hello World”开始

  1. 导入依靠:咱们直接用4.x最新的版别
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.92.Final</version>
        </dependency>
  1. 编写代码:那么咱们就开端编写这个Demo的服务器和客户端相关代码

    • NettyServer:根据Netty的客户端

      /**
       * <p>Date: 2023/5/14 10:29</p>
       * <p>Author: fighter3</p>
       * <p>Description: Netty服务端Demo</p>
       */
      public class NettyServer{
          // 服务器监听的端口号
          private int port;
          public NettyServer(int port) {
              this.port = port;
          }
          /**
           * 发动Netty服务器
           * @throws InterruptedException
           */
          public void run() throws InterruptedException {
              // 创立boss线程组和worker线程组
              // bossGroup 用于监听客户端的衔接恳求,将衔接恳求发送给 workerGroup 进行处理
              NioEventLoopGroup bossGroup = new NioEventLoopGroup();
              // workerGroup 用于处理客户端衔接的数据读写
              NioEventLoopGroup workerGroup = new NioEventLoopGroup();
              try {
                  // 创立 ServerBootstrap 目标,用于发动 Netty 服务器
                  ServerBootstrap serverBootstrap = new ServerBootstrap();
                  // 绑定线程池事情组
                  serverBootstrap.group(bossGroup, workerGroup)
                          .channel(NioServerSocketChannel.class)
                          // 通道初始化回调函数,在发动的时分能够主动调用
                          .childHandler(new ChannelInitializer<SocketChannel>() {
                              @Override
                              public void initChannel(SocketChannel ch) throws Exception {
                                  ChannelPipeline pipeline = ch.pipeline();
                                  // 增加音讯处理器
                                  pipeline.addLast(new NettyServerHandler());
                              }
                          });
                  // 绑定端口,开端接纳客户端恳求
                  ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
                  System.out.println("Netty服务器监听端口:"+port);
                  // 等候服务端监听端口关闭
                  channelFuture.channel().closeFuture().sync();
              } finally {
                  //开释线程组资源
                  bossGroup.shutdownGracefully();
                  workerGroup.shutdownGracefully();
              }
          }
          public static void main(String[] args) throws InterruptedException {
              // 创立服务器目标,监听端口号为 8888
              NettyServer server = new NettyServer(8888);
              System.out.println("============Netty服务器发动...=============");
              // 发动服务器
              server.run();
              System.out.println("============Netty服务器停止...=============");
          }
      }
      
    • NettyServerHandler:服务器的音讯处理器,用于处理各种事情

      /**
       * <p>Date: 2023/5/14 10:30</p>
       * <p>Author: fighter3</p>
       * <p>Description: Netty服务器音讯处理器</p>
       */
      public class NettyServerHandler extends ChannelInboundHandlerAdapter {
          /**
           * 当客户端上线的时分会触发这个办法
           * @param ctx
           * @throws Exception
           */
          @Override
          public void channelActive(ChannelHandlerContext ctx) throws Exception {
              String message="你好,靓仔!";
              ByteBuf hello = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8);
              // 发送音讯
              ctx.writeAndFlush(hello);
          }
          /**
           *当 Channel 中有来自客户端的数据时就会触发这个办法
           */
          @Override
          public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
              ByteBuf buf = (ByteBuf) msg;
              System.out.println("客户端发来的音讯:" + buf.toString(CharsetUtil.UTF_8)); // 接纳音讯并打印输出
          }
          /**
           * 当有反常时触发这个办法
           */
          @Override
          public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
              cause.printStackTrace();
              ctx.close();
          }
      }
      
  • NettyClient:运用Netty的客户端,通过ip和端口衔接服务端

    /**
     * <p>Date: 2023/5/14 10:32</p>
     * <p>Author: fighter3</p>
     * <p>Description: Netty客户端Demo</p>
     */
    public class NettyClient {
        // 服务器 IP
        private String host;
        // 服务器监听的端口号
        private int port;
        public NettyClient(String host, int port) {
            this.host = host;
            this.port = port;
        }
        /**
         * 发动 Netty 客户端
         */
        public void run() throws InterruptedException {
            // 创立事情循环组
            NioEventLoopGroup group = new NioEventLoopGroup();
            try {
                // 创立 Bootstrap 目标
                Bootstrap bootstrap = new Bootstrap();
                // 装备 Bootstrap 目标
                // 设置线程组
                bootstrap.group(group)
                        // 设置客户端通讯的通道类型为NIO类型
                        .channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            // 通道初始化回调函数,在发动的时分能够主动调用
                            @Override
                            public void initChannel(SocketChannel ch) throws Exception {
                                // 增加音讯处理器
                                ch.pipeline().addLast(new NettyClientHandler());
                            }
                        });
                // 衔接服务器,异步等候衔接成功
                ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
                System.out.println("===========Netty客户端衔接服务端=========");
                // 等候客户端衔接关闭
                channelFuture.channel().closeFuture().sync();
            } finally {
                //开释资源
                group.shutdownGracefully();
            }
        }
        public static void main(String[] args) throws InterruptedException {
           // 创立客户端目标,并衔接到服务器
            NettyClient client = new NettyClient("127.0.0.1", 8888);
            // 发动客户端,开端发送音讯
            client.run();
        }
    }
    
  • NettyClientHandler:Netty客户端处理器,用于处各种事情

    /**
     * <p>Date: 2023/5/14 10:33</p>
     * <p>Author: fighter3</p>
     * <p>Description: Netty客户端处理器</p>
     */
    public class NettyClientHandler extends ChannelInboundHandlerAdapter {
        /**
         * 当 Channel 准备就绪时就会触发这个办法
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            String message="大佬,带带我!";
            ByteBuf hello = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8);
            // 发送音讯
            ctx.writeAndFlush(hello);
        }
        /**
         * 当 Channel 中有来自服务器的数据时就会触发这个办法
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf buf = (ByteBuf) msg;
            System.out.println("服务端发来的音讯:" + buf.toString(CharsetUtil.UTF_8)); // 接纳音讯并打印输出
        }
        /**
         * 产生反常就会触发这个办法
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }
    
  1. 运转一下:先发动NettyServer,再发动NettyClient,看下运转结果

    ============Netty服务器发动...=============
    Netty服务器监听端口:8888
    客户端发来的音讯:大佬,带带我!
    
    ===========Netty客户端衔接服务端=========
    服务端发来的音讯:你好,靓仔!
    

好了,一个简略的Netty入门Demo就写完了,Netty是一个双工通讯的网络结构,能够看到,服务端和客户端,流程基本上共同,首要包含这么几个过程:

  1. 创立事情循环组和相关目标,用于监听和处理网络事情;
  2. 装备 Netty 服务器或客户端的发动参数,包含线程组、通道类型、TCP 参数等;
  3. 给服务器或客户端的 ChannelPipeline 增加各种 ChannelHandler,用于处理不同的网络事情;
  4. 绑定端口发动服务器或衔接服务器;
  5. 等候服务器或客户端衔接关闭,开释相关资源。

玩转Netty,从“Hello World”开始

虽然这个Demo比较简略,但其实现已用到了Netty里几个比较要害的组件:

  1. ByteBuf:Netty 的字节容器,类似于 Java 的 ByteBuffer,可是供给了愈加强大、简便且安全的 API,用于在网络中传递二进制数据;
  2. EventLoopGroup:Netty 的事情循环组,用于管理和调度衔接到服务器或许从服务器衔接出去的所有 Channel 上的事情循环;
  3. ServerBootstrap:Netty 的服务器发动类,用于发动和装备一个 TCP/IP 服务器;
  4. Bootstrap:Netty 的客户端发动类,用于发动和装备一个 TCP/IP 客户端;
  5. Channel:Netty 的中心概念,用于表明一个通讯通道,能够读取和写入数据;
  6. ChannelPipeline:Netty 的 Channel 处理器,用于在传入的数据上执行一组 ChannelHandler;
  7. ChannelHandler:Netty 的中心组件,用于处理各种通讯事情,例如读取数据、写数据、树立衔接等;

玩转Netty,从“Hello World”开始

后续,咱们还会和这些组件打更多的交道。


好了,那么这期内容就到这了,这期里咱们初步了解了Netty,包含什么是Netty、Netty现状、Netty的应用,还写了一个简略的Demo。下一期,咱们持续深入了解Netty,敬请期待。



参考:

[1].netty.io/

[2].《Netty威望指南》

[3]. 《Netty中心原理剖析与RPC实践》