Netty入门与实战

介绍

Netty是一个高性能事件驱动的异步的非堵塞的IO(NIO)网络应用程序框架和工具,基于Netty可以建立高性能的Http服务器。支持HTTP、 WebSocket 、Protobuf、 TCP 和UDP等协议。
通过借助Netty,你可以很容易的自己去实现HTTP、FTP、UDP、RPC、WebSocket、Redis的Proxy、MySQL的Proxy服务器等等。

Netty 整体结构

直接引用官网的图:

img

  1. Core 核心层

    它提供了底层网络通信的通用抽象和实现,包括可扩展的事件模型、通用的通信 API、支持零拷贝的 ByteBuf 等。

  2. Protocol Support 协议支持层

    协议支持层包括了主流的协议的编解码实现,如 Http、Websocket、Protobuf、大文件传输、文本、二进制等,此外还支持自定义的应用层协议。

  3. Transport Service 传输服务层

    传输服务层提供了网络传输能力的定义和实现方法。支持 Socket、Http 隧道等传输方式。对 TCP、UDP 等数据传输做了抽象和封装,让使用者增加聚焦在业务逻辑的开发上,而不必关系底层数据传输的处理细节。

Netty 逻辑架构

Netty 的逻辑处理架构属于典型的网络分层架构,分为网络通信层、事件调度层、服务编排层。

网络通信层的主要职责是:执行网络 IO 的操作。支持多种网络协议和 I/O 模型的连接操作。核心组件包括:BootStrap、ServerBootStrap、Channel 三个组件。

事件调度层的职责是通过 Reactor 线程模型对各类事件进行聚合处理,通过 Selector 主循环线程集成多种事件( I/O 事件、信号事件、定时事件等),实际的业务处理逻辑是交由服务编排层中相关的 Handler 完成。核心组件包括:EventLoopGroup、EventLoop。

服务编排层的职责是负责组装各类服务,它是 Netty 的核心处理链,用以实现网络事件的动态编排和有序传播。核心组件包括:ChannelPipeline、ChannelHandler、ChannelHandlerContext。

Netty 源码结构

Core 核心层模块:netty-common 模块、netty-buffer 模块、netty-resover 模块。

Protocol Support 协议支持层模块:netty-codec 模块、netty-handler 模块。

Transport Service 传输服务层模块:netty-transport 模块。

为什么使用Netty

首先说一下为什么不使用NIO的原因:

  • NIO的类库和API繁杂。需要很多额外的技能做铺垫。例如需要很熟悉Java多线程编程、Selector线程模型。导致工作量和开发难度都非常大。
  • 扩展 ByteBuffer:NIO和Netty都有ByteBuffer来操作数据,但是NIO的ByteBuffer长度固定而且操作复杂,许多操作甚至都需要自己实现。而且它的构造函数是私有,不能扩展。Netty 提供了自己的
    ByteBuffer 实现, Netty 通过一些简单的 APIs 对 ByteBuffer 进行构造、使用和操作,以此来解决 NIO 中的一些限制。
  • NIO 对缓冲区的聚合和分散操作可能会操作内存泄露,到jdk7才解决了内存泄露的问题
  • 存在臭名昭著的epoll bug,导致Selector空轮询:这个bug会导致linux上导致cpu 100%,使得nio server/client不可用,直到jdk 6u4才解决。

相比于NIO,netty的优点就很明显了:

  • API使用简单,开发门槛低。
  • 功能强大,预置了多种编解码功能,支持多种协议开发。
  • Netty自带拆包解包,从NIO各种繁复的细节中脱离出来,定制能力强,可以通过ChannelHadler进行扩展。
  • 对Selector做了很多细小的优化,性能高,对比其它NIO框架,Netty综合性能最优。
  • 经历了大规模的应用验证。在互联网、大数据、网络游戏、企业应用、电信软件得到成功,很多著名的框架通信底层就用了Netty,比如Dubbo
  • Netty解决了NIO中Selector空轮询BUG。
  • Netty底层IO模型可以随意切换,比如可以从NIO切换到BIO

Netty 的应用场景

  1. 互联网行业
    1. 互联网行业: 在分布式系统中, 各个节点之间需要远程服务调用, 高性能的 RPC 框架必不可少, Netty 作为
      异步高性能的通信框架, 往往作为基础通信组件被这些 RPC 框架使用。
    2. 典型的应用有: 阿里分布式服务框架 Dubbo 的 RPC 框架使用 Dubbo 协议进行节点间通信, Dubbo 协议默
      认使用 Netty 作为基础通信组件, 用于实现各进程节点之间的内部通信
  2. 游戏行业
    1. 无论是手游服务端还是大型的网络游戏, Java 语言得到了越来越广泛的应用
    2. Netty 作为高性能的基础通信组件, 提供了 TCP/UDP 和 HTTP 协议栈, 方便定制和开发私有协议栈, 账号登
      录服务器
    3. 地图服务器之间可以方便的通过 Netty 进行高性能的通信
  3. 大数据领域
    1. 经典的 Hadoop 的高性能通信和序列化组件 Avro 的 RPC 框架, 默认采用 Netty 进行跨界点通信
    2. 它的 Netty Service 基于 Netty 框架二次封装实现。

Netty实战应用

服务端启动类

Java代码如下:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 * 应答服务器
 */
public class EchoServer {

    private int port;

    public EchoServer(int port) {
        this.port = port;
    }

    public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class) // (3)
             .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                  ch.pipeline().addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
                     ch.pipeline().addLast("decoder", new StringDecoder());
                     ch.pipeline().addLast("encoder", new StringEncoder());
                     ch.pipeline().addLast(new EchoServerHandler());
                 }
             })
             .option(ChannelOption.SO_BACKLOG, 128)          // (5)
             .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)

            // 绑定端口,开始接收进来的连接
            ChannelFuture f = b.bind(port).sync(); // (7)

      System.out.println("Server start listen at " + port );
            // 等待服务器  socket 关闭 。
            // 在这个例子中,这不会发生,但你可以优雅地关闭你的服务器。
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        } else {
            port = 8080;
        }
        new EchoServer(port).run();
    }
}

  • 两个NioEventLoopGroup对象,可以看作两个线程组。bossGroup的作用是监听客户端请求。workerGroup的作用是处理每条连接的数据读写。
  • ServerBootstrap是一个引导类,其对象的作用是引导服务器的启动工作。
  • .group是配置上面两个线程组的角色,也就是谁去监听谁去处理读写。上面只是创建了两个线程组,并没有实际使用。
  • .channel是配置服务端的IO模型,上面代码配置的是NIO模型。也可以配置为BIO,如OioServerSocketChannel.class。
  • .childHandler用于配置每条连接的数据读写和业务逻辑等。上面代码用的是匿名内部类,并没有什么内容。实际使用中为了规范起见,一般会再写一个单独的类也就是初始化器,在里面写上需要的操作。就如Netty实战那篇中的代码一样。
  • 最后就是绑定监听端口了。

引导类最小化的参数配置就是如上四个:配置线程组、IO模型、处理逻辑、绑定端口。

服务端处理逻辑类

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;

/**
 * 处理服务端 channel.
 */
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
     System.out.println(ctx.channel().remoteAddress()+"->Server :"+ msg.toString());
        ctx.write(msg); // (1)



        ChannelFuture future= ctx.channel().write(msg);
        future.addListener(new GenericFutureListener<Future<? super Void>>() {
   @Override
   public void operationComplete(Future<? super Void> future) throws Exception {
    System.out.println("asdf");
   }
  });


        ctx.flush(); // (2)
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 当出现异常就关闭连接
        cause.printStackTrace();
        ctx.close();
    }
}

客户端类

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 * Sends one message when a connection is open and echoes back any received
 * data to the server.  Simply put, the echo client initiates the ping-pong
 * traffic between the echo client and server by sending the first message to
 * the server.
 */
public final class EchoClient {

    static final String HOST = System.getProperty("host", "127.0.0.1");
    static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));
    static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));

    public static void main(String[] args) throws Exception {

        // Configure the client.
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .option(ChannelOption.TCP_NODELAY, true)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                  p.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
                     p.addLast("decoder", new StringDecoder());
                     p.addLast("encoder", new StringEncoder());
                     p.addLast(new EchoClientHandler());
                 }
             });

            // Start the client.
            ChannelFuture f = b.connect(HOST, PORT).sync();

            // Wait until the connection is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down the event loop to terminate all threads.
            group.shutdownGracefully();
        }
    }
}

客户端处理逻辑类

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

public class EchoClientHandler extends ChannelInboundHandlerAdapter {

 private final ByteBuf firstMessage;

    /**
      * Creates a client-side handler.
       */
      public EchoClientHandler() {
         firstMessage = Unpooled.buffer(EchoClient.SIZE);
         for (int i = 0; i < firstMessage.capacity(); i ++) {
              firstMessage.writeByte((byte) i);
         }
      }

      @Override
      public void channelActive(ChannelHandlerContext ctx) {
          ctx.writeAndFlush(firstMessage);
      }

      @Override
      public void channelRead(ChannelHandlerContext ctx, Object msg) {
         ctx.write(msg);
      }

     @Override
      public void channelReadComplete(ChannelHandlerContext ctx) {
         ctx.flush();
      }

     @Override
     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {         // Close the connection when an exception is raised.
          cause.printStackTrace();
         ctx.close();
      }
  }

总结

  • 为了尽可能地提升性能,Netty 在很多地方进行了无锁化的设计,例如在 I/O 线程内部进行串行操作,避免多线程竞争导致的性能下降问题。表面上看,串行化设计似乎 CPU 利用率不高,并发程度不够。但是,通过调整 NIO 线程池的线程参数,可以同时启动多个串行化的线程并行运行,这种局部无锁化的串行线程设计相比一个队列—多个工作线程的模型性能更优。 Netty 的 NioEventLoop 读取到消息之后,直接调用 ChannelPipeline 的 fireChannelRead (Object msg)。只要用户不主动切换线程,一直都是由 NioEventLoop 调用用户的 Handler,期间不进行线程切换。这种串行化处理方式避免了多线程操作导致的锁的竞争,从性能角度看是最优的。

  • 使用Reactor设计思路,提高了Netty框架拓展性和复用性

  • Netty 的 Promise 接口扩展了 Netty 的 Future 接口,它表示一种可写的 Future,就是可以设置异步执行的结果。

  • channel使用组合方式,将IO操作都交给Unsafe接口及其子类实现,减少耦合。

  • Netty 的 ByteBuf 采用了读写索引分离的策略(readerIndex 与 writerIndex),一个初始化(里面尚未有任何数据)的 ByteBuf 的 readerIndex 与 writerIndex 值都为 0

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值