• Netty 权威指南笔记二Java NIO 和 Netty 对比
  • Java NIO 开发
  • Netty 开发
  • 示例程序


Netty 权威指南笔记(二):Java NIO 和 Netty 对比

Netty 是业界流行的 NIO 框架之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都说首屈一指的,也已经得到了成百上千商用项目的验证。Netty 框架都有什么优点呢?

  1. API 使用简单,开发门槛低。
  2. 功能强大,预置多种编解码功能,支持多种主流协议。
  3. 定制能力强,可以通过 ChannelHandler 对通信框架灵活扩展。
  4. 性能高。
  5. 成熟稳定,社区活跃,已经修复了 Java NIO 所有的 Bug。
  6. 经历了大规模商业应用的考验,质量有保证。

Java NIO 开发

我们看一看在 笔记(一)里面的 Java NIO 示例程序,回顾一下使用 NIO 开发服务端程序的步骤:

  1. 创建 ServerSocketChannel 和业务处理线程池。
  2. 绑定监听端口,并配置为非阻塞模式。
  3. 创建 Selector,将之前创建的 ServerSocketChannel 注册到 Selector 上,监听 SelectionKey.OP_ACCEPT。
  4. 循环执行 Selector.select() 方法,轮询就绪的 Channel。
  5. 轮询就绪的 Channel 时,如果是处于 OP_ACCEPT 状态,说明是新的客户端接入,调用 ServerSocketChannel.accept 接收新的客户端。
  6. 设置新接入的 SocketChannel 为非阻塞模式,并注册到 Selector 上,监听 OP_READ。
  7. 如果轮询的 Channel 状态是 OP_READ,说明有新的就绪数据包需要读取,则构造 ByteBuffer 对象,读取数据。

Netty 开发

在讲 Netty 优点的时候,说到“Netty API 使用简单,开发门槛低”。先看看下面的 Netty TimeServer 示例程序,看看 Netty 开发都有哪些步骤?

  1. 创建 NIO 线程组 EventLoopGroup 和 ServerBootstrap。
  2. 设置 ServerBootstrap 的属性:线程组、SO_BACKLOG 选项,设置 NioServerSocketChannel 为 Channel,设置业务处理 Handler。
  3. 绑定端口,启动服务器程序。
  4. 在业务处理 TimeServerHandler 中,读取客户端发送的数据,并给出响应。

那么相比 Java NIO,使用 Netty 开发程序,都简化了哪些步骤呢?

  1. OP_ACCEPT 的处理被简化,因为对于 accept 操作的处理在不同业务上都是一致的。
  2. 在 NIO 中需要自己构建 ByteBuffer 从 Channel 中读取数据,而 Netty 中数据是直接读取完成存放在 ByteBuf 中的。相当于省略了用户进程从内核中复制数据的过程。
  3. 在 Netty 中,我们看到有使用一个解码器 FixedLengthFrameDecoder,可以用于处理定长消息的问题,能够解决 TCP 粘包读半包问题,十分方便。

示例程序

服务器端程序

public class TimeServer {
    public void bind(int port) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 2014)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(16));
                            socketChannel.pipeline().addLast(new TimeServerHandler());
                        }
                    });

            ChannelFuture future = bootstrap.bind(port).sync();

            System.out.println("start listening ...");

            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 11000;
        new TimeServer().bind(port);
    }
}

TimeServerHandler 业务处理类:

public class TimeServerHandler extends ChannelHandlerAdapter {
    private int counter = 0;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;

        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);

        String body = new String(req, "UTF-8");
        System.out.println("time server receive: " + body + ", counter: " + counter++);

        String currentTime = "QUERY TIME ORDER".equals(body) ? new Date().toString() : "BADY ORDER";

        ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
        ctx.write(resp);
    }

    /**
     * 读完一批次数据,就会调用一次 channelReadComplete
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelReadComplete");
        // flush 之后,之前 write 的数据,才会发送出去
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("exceptionCaught");
        cause.printStackTrace();
        ctx.close();
    }
}

客户端程序

public class TimeClient {

    public void connect(int port, String host) throws InterruptedException {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

        try {
            List<VirtualMachineDescriptor> list = VirtualMachine.list();
            for (VirtualMachineDescriptor descriptor : list) {
                System.out.println(descriptor.displayName());
            }

            System.out.println("vm: " + list.size());

            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(28));
                            socketChannel.pipeline().addLast(new TimeClientHandler());
                        }
                    });

            ChannelFuture channelFuture = bootstrap.connect(host, port).sync();

            channelFuture.channel().closeFuture().sync();

            System.out.println("end listening ...");
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 11000;
        new TimeClient().connect(port, "127.0.0.1");
    }
}

TimeClientHandler 客户端业务处理类:

public class TimeClientHandler extends ChannelHandlerAdapter {

    private final byte[] req;
    private int counter = 0;

    public TimeClientHandler() {
        System.out.println("init TimeClientHandler");
        req = "QUERY TIME ORDER".getBytes();

    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 100; ++i) {
            ByteBuf firstMessage = Unpooled.buffer(req.length);
            firstMessage.writeBytes(req);
            ctx.writeAndFlush(firstMessage);
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("channelRead");

        ByteBuf buf = (ByteBuf) msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);

        System.out.println("now is: " + new String(req, "UTF-8") + ", counter: " + counter++);

        if (counter > 99) {
            ctx.close();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("exceptionCaught");
        ctx.close();
    }
}