高性能IO框架Netty一-第一个Netty程序

x33g5p2x  于2021-12-21 转载在 其他  
字(5.7k)|赞(0)|评价(0)|浏览(376)

Netty作为一个高性能IO框架,基本上所有使用JAVA技术栈的大厂,底层的IO通信框架都是通过Netty实现的。例如 dubbo,Spring gateway等等。所以不管是已经工作的还是在校学生。学会Netty,在你面试大厂的时候,无疑都是加分项。是所有从事JAVA工作的必备技能。完整介绍Netty的书籍不是很多,主要有华为大牛李林峰的《Netty权威指南》,和上年刚出的《Netty进阶:跟着案例学Netty》,以及国外的译本《Netty实战》。初学者建议选择《Netty权威指南》或者《Netty实战》。等学会之后在看《Netty进阶》。

中间一个月比较忙,鸽了三天,今天开始更新Netty相关文章.

一、Netty 简介

1、Netty是什么?

以下资料摘自百度百科,总结的已经比较好了。

Netty是由Jboss提供的一个Java开源框架,现为 Github上的独立项目。Netty提供异步的、时间驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。

也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。

2、为什么要用Netty?

1、虽然JAVA NIO框架提供了 多路复用IO的支持,但是并没有提供上层“信息格式”的良好封装。例如前两者并没有提供针对 Protocol Buffer、JSON这些信息格式的封装,但是Netty框架提供了这些数据格式封装(基于责任链模式的编码和解码功能);

2、NIO的类库和API相当复杂,使用它来开发,需要非常熟练地掌握Selector、ByteBuffer、ServerSocketChannel、SocketChannel等,需要很多额外的编程技能来辅助使用NIO,例如,因为NIO涉及了Reactor线程模型,所以必须必须对多线程和网络编程非常熟悉才能写出高质量的NIO程序

3、要编写一个可靠的、易维护的、高性能的NIO服务器应用。除了框架本身要兼容实现各类操作系统的实现外。更重要的是它应该还要处理很多上层特有服务,例如:客户端的权限、还有上面提到的信息格式封装、简单的数据读取,断连重连,半包读写,心跳等等,这些Netty框架都提供了响应的支持。

4、高性能,拥有比核心 Java API 更好的吞吐量,较低的延时,并且资源消耗更少,这个得益于共享池和重用,减少内存拷贝,实现0拷贝。

4、JAVA NIO框架存在一个poll/epoll bug:Selector doesn’t block on Selector.select(timeout),不能block意味着CPU的使用率会变成100%(这是底层JNI的问题,上层要处理这个异常实际上也好办)。当然这个bug只有在Linux内核上才能重现。

这个问题在JDK 1.7版本中还没有被完全解决,但是Netty已经将这个bug进行了处理。

这个Bug与操作系统机制有关系的,JDK虽然仅仅是一个兼容各个操作系统平台的软件,但在JDK5和JDK6最初的版本中(严格意义上来将,JDK部分版本都是),这个问题并没有解决,而将这个帽子抛给了操作系统方,这也就是这个bug最终一直到2013年才最终修复的原因(JDK7和JDK8之间)。

3、为什么Netty使用NIO而不是AIO?

Netty不看重Windows上的使用,在Linux系统上,AIO的底层实现仍使用EPOLL,没有很好实现AIO,因此在性能上没有明显的优势,而且被JDK封装了一层不容易深度优化。

AIO还有个缺点是接收数据需要预先分配缓存, 而不是NIO那种需要接收时才需要分配缓存, 所以对连接数量非常大但流量小的情况, 内存浪费很多。

据说Linux上AIO不够成熟,处理回调结果速度跟不上处理需求,有点像外卖员太少,顾客太多,供不应求,造成处理速度有瓶颈。

作者原话:

Not faster than NIO (epoll) on unix systems (which is true)

There is no daragram suppport

Unnecessary threading model (too much abstraction without usage)

4、为什么不用Netty5

  1. netty5 中使用了 ForkJoinPool,增加了代码的复杂度,但是对性能的改善却不明显

  2. 多个分支的代码同步工作量很大

  3. 作者觉得当下还不到发布一个新版本的时候

  4. 在发布版本之前,还有更多问题需要调查一下,比如是否应该废弃 exceptionCaught, 是否暴露EventExecutorChooser等等。

二、Hello Netty!

接下来我们就开始从一个简单地demo,进入netty的世界吧。

该demo实现了创建一个Netty服务器和一个netty客户端,服务端接收到客户端请求的时候便打印相应的信息。

1、NettyServer

/**
 * 作者:DarkKing
 * 创建日期:2019/10/02
 * 类说明:netty服务端
 *
 */
public class NettyServer  {

    private final int port;

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

    public static void main(String[] args) throws InterruptedException {
        int port = 9999;
        NettyServer echoServer = new NettyServer(port);
        System.out.println("服务器启动");
        echoServer.start();
        System.out.println("服务器关闭");
    }

    public void start() throws InterruptedException {
        final NettyServerHandler serverHandler = new NettyServerHandler();
        /*线程组*/
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            /*服务端启动必须*/
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)/*将线程组传入*/
                    .channel(NioServerSocketChannel.class)/*指定使用NIO进行网络传输*/
                    .localAddress(new InetSocketAddress(port))/*指定服务器监听端口*/
                    /*服务端每接收到一个连接请求,就会新启一个socket通信,也就是channel,
                    所以下面这段代码的作用就是为这个子channel增加handle*/
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel ch) throws Exception {
                            /*添加到该子channel的pipeline的尾部*/
                            ch.pipeline().addLast(serverHandler);
                        }
                    });
            ChannelFuture f = b.bind().sync();/*异步绑定到服务器,sync()会阻塞直到完成*/
            f.channel().closeFuture().sync();/*阻塞直到服务器的channel关闭*/

        } finally {
            group.shutdownGracefully().sync();/*优雅关闭线程组*/
        }
    }
}

2、NettyServerHandler

/**
 * 作者:DarkKing
 * 创建日期:2019/10/02
 * 类说明:netty服务端处理handler
 *
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    /*客户端读到数据以后,就会执行*/
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf in = (ByteBuf)msg;
        System.out.println("Server accept"+in.toString(CharsetUtil.UTF_8));
        ctx.write(in);

    }
    /*** 服务端读取完成网络数据后的处理*/
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
                .addListener(ChannelFutureListener.CLOSE);
    }
    /*** 发生异常后的处理*/
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

3、NettyClient

public class NettyClient {

    private final int port;
    private final String host;

    public NettyClient(int port, String host) {
        this.port = port;
        this.host = host;
    }

    public void start() throws InterruptedException {
        /*线程组*/
        EventLoopGroup group = new NioEventLoopGroup();
        try{
            /*客户端启动必备*/
            Bootstrap b = new Bootstrap();
            b.group(group)/*把线程组传入*/
                    /*指定使用NIO进行网络传输*/
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host,port))
                    .handler(new NettyClientHandler());
            /*连接到远程节点,阻塞直到连接完成*/
            ChannelFuture f = b.connect().sync();
            /*阻塞程序,直到Channel发生了关闭*/
            f.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully().sync();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        new NettyClient(9999,"127.0.0.1").start();
    }
}

4、NettyClientHandler

/**
 * 作者:DarkKing
 * 创建日期:2019/10/02
 * 类说明:netty客户端处理器
 *
 */
public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    /*客户端读到数据以后,就会执行*/
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg)
            throws Exception {
        System.out.println("client acccept:"+msg.toString(CharsetUtil.UTF_8));
    }

    /*连接建立以后*/
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer(
                "Hello Netty",CharsetUtil.UTF_8));
        //ctx.fireChannelActive();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();

        ctx.close();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);
    }
}

5、程序演示

1、启动NettyServer,打印服务器启动

2、启动客户端,客户端连接服务器端成功,并发送Hello,Netty!并收到服务器端返回的消息,Hello,Netty!

3、服务器端打印 Server accept:Hello Netty

6、总结

根据上一章网络编程四-原生JDK的NIO及其应用相比,使用Netty大大简化了我们的开发工作量,并将原生JDK复杂的Selector、ByteBuffer、ServerSocketChannel、SocketChannel等组件进行封装。使我们开发者不需要关系具体底层的运行原理和机制。进行模板化的开发。提高开发的效率、以及容错率。

本章主要简单介绍了下Netty的入门。代码已放到github上,GitHub - 379685397/netty: csdn博客代码。下章对Netty的组件进行介绍。

相关文章