Netty

Netty是一个异步的、甚于事件驱动的网络应用框架,用于快速开发可维护、高性能的网络服务器和客户端

1.使用

  • 服务端
public static void main(String[] args) {
        // 服务启动器
        new ServerBootstrap()
                .group(new NioEventLoopGroup())
                // TCP SOCKET通道为NioServerSocketChannel
                // UDP DatagramChannel
                .channel(NioServerSocketChannel.class)
                // 当客户端注册读写事件时 初始化Handler
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                System.out.println(msg);
                            }
                        });
                    }
                })
                .bind(8080);
    }
  • 客户端
public static void main(String[] args) throws Exception {
        // 启动类
        new Bootstrap()
                // 添加EventLoop
                .group(new NioEventLoopGroup())
                // 客户端channel实现
                .channel(NioSocketChannel.class)
                // 处理器
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    /**
                     * 连接后被调用
                     * @param ch
                     * @throws Exception
                     */
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringEncoder());
                    }
                })
                .connect(new InetSocketAddress("localhost", 8080))
                .sync()
                .channel()
                .writeAndFlush("Netty");
    }

2.EventLoop

EventLoop本质是一个单线程执行器 (同时维护了一个Selector)。里面有run方法处理Channel上源源不断的io事件
它的继承关系比较复杂

  • 一条线是继承自j.u.c.ScheduledExecutorService因此包含了线程池中所有的方法
  • 另一条线是继承自netty自己的OrderedEventExecutor
    -- 提供了boolean InEventloop(Thread thread)方法判断一个钱程是否属于此EventLoop
    -- 提供了parent方法来看看自己属于哪个EventoopGroup

事件循环组
EventoopGroup是一组EventLoop, Channel 一般会调用EventLoopGroup的register方法来绑定其中一个EventLoop
后续这个Channel上的io事件都由此EventLoop来处理(保证了io事件处理时的线程安全)

  • 继承自netty自己的EventExecutorGroup
    -- 实现了Iterable接口提供遍历EventLoop的能力
    -- 有next方法获取集合中下一个EventLoop
    EventoopGroup里线程循环处理channel

2.1 基本使用

// 可以处理io 普通 定时任务
// 可以指定线程数 默认cpu * 2
EventLoopGroup event = new NioEventLoopGroup();
// 可以处理 普通 定时任务
EventLoopGroup eventLoopGroup1 = new DefaultEventLoop();
// 获取下一个事件
event.next();
// 执行普通任务
event.next().submit(() -> {
    System.out.println("123");
});
// 执行定时任务 初始执行时间0s 间隔1s 单位秒
event.next().scheduleAtFixedRate(() -> {
    System.out.println("321");
}, 0, 1, TimeUnit.SECONDS);

2.2 分工

new ServerBootstrap()
                /**
                 * 可以划分为boss 和 worker
                 * boss负责ServerSocketChannel上的accept worker
                 * worker负责SocketChannel上的读写
                 */
                .group(new NioEventLoopGroup(), new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                System.out.println(msg);
                            }
                        });
                    }
                })
                .bind(8080);

2.3 处理长handler

EventLoopGroup group = new DefaultEventLoopGroup();
        new ServerBootstrap()
                .group(new NioEventLoopGroup(), new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {

                    /**
                     * 处理handler时间比较长
                     * @param ch
                     * @throws Exception
                     */
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(group, "name", new ChannelInboundHandlerAdapter() {
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                System.out.println(msg);
                                // 传递给下一个handler
                                ctx.fireChannelRead(msg);
                            }
                        }).addLast(group, "name1", new ChannelInboundHandlerAdapter() {
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                System.out.println(msg);
                            }
                        });
                    }
                })
                .bind(8080);

3.channel

channel的主要作用

  • close 可以用来关闭channel
  • closeFuture 用来处理channel的关闭
    -- sync方法作用是同步等待channel关闭
    -- 而addListener方法是异步等待channel关闭
  • pipeline() 方法添加处理器
  • write 方法将数据写入channel缓冲区,但不发送
  • flush 立刻发送
  • writeAndFlush() 方法将数据写入并刷出

3.1 channelFuture

  • 异步问题
public static void main(String[] args) throws Exception {
        ChannelFuture channelFuture = new Bootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    /**
                     * 连接后被调用
                     * @param ch
                     * @throws Exception
                     */
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringEncoder());
                    }
                })
                // 异步非阻塞 main调用 nio线程连接
                .connect(new InetSocketAddress("localhost", 8080));

        // 1.阻塞一下 以获取连接到的channel
//        channelFuture.sync();
//        Channel channel = channelFuture.channel();
//        channel.writeAndFlush("123");

        // 2.异步处理结果
        channelFuture.addListener(new ChannelFutureListener() {
            /**
             * 在nio线程里连接成功后调用
             * @param future
             * @throws Exception
             */
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                Channel channel = future.channel();
                channel.writeAndFlush("123");
            }
        });

    }
  • 异步问题
NioEventLoopGroup group = new NioEventLoopGroup();
new Thread( () -> {
            Scanner scanner = new Scanner(System.in);
            while(true) {
                String line = scanner.nextLine();
                if ("q".equals(line)) {
                    // 关闭也是异步的 在这里不能处理关闭操作
                    channel.close();
                    break;
                }
                channel.writeAndFlush(line);
            }
        }, "name").start();

        /**
         * 处理关闭操作
         * 1.同步
         * 2.异步
         */
        ChannelFuture closeFuture = channel.closeFuture();
        closeFuture.sync();

        closeFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
              group.shutdownGracefully();
            }
        });

4.Future Promise

在异步处理时,经常用到这两个接口
首先要说明netty中的Future与jdk中的Future同名,但是是两个接口,netty 的Future继承自jdk的Future
而Promise又对netty Future进行了扩展

  • jdk Future只能同步等待任务结束(或成功、或失败)才能得到结果
  • netty Future可以同步等待任务结束得到结果,也可以异步方式得到结果,但都是要等任务结束
  • netty Promise不仅有netty Future的功能,而且脱离了任务独立存在,只作为两个线程间传递结果的容器

4.1 Future

public static void main(String[] args) throws Exception {
        NioEventLoopGroup group = new NioEventLoopGroup();
        EventLoop eventLoop = group.next();
        Future<Integer> future = eventLoop.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return null;
            }
        });
        // 同步获取结果
        future.get();
        // 异步
        future.addListener(new GenericFutureListener<Future<? super Integer>>() {
            @Override
            public void operationComplete(Future<? super Integer> future) throws Exception {
                future.getNow();
            }
        });
    }

4.2 Promise

public static void main(String[] args) throws Exception {
        EventLoop loop = new NioEventLoopGroup().next();
        DefaultPromise<Integer> promise = new DefaultPromise<Integer>(loop);
        new Thread(() -> {
            // 设置结果
            promise.setSuccess(100);
        }).start();
        // 获取结果
        promise.get();
    }

5.Handler Pipeline

ChannelHandler用来处理Channel上的各种事件,分为入站、出站两种。
所有ChannelHandler被连成一串,就是Pipeline

  • 入站处理器通常是ChannellnboundHandlerAdapter的子类,主要用来读取客户端数据,写回结果
  • 出站处理器通常是ChannelOutboundHandlerAdapter的子类,主要对写回结果进行加工
    打个比喻,每个Channel是一个产品的加工车间,Pipeline 是车间中的流水线
    ChannelHandler 就是流水线上的各道工序,ByteBuf是原材料,经过很多工序的加工:先经过一道道入站工序,再经过一道道出站工序最终变成产品
 public static void main(String[] args) {
        new ServerBootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        /**
                         * 处理器顺序
                         * head -> h1 -> h2 -> tail
                         */
                        pipeline.addLast("h1", new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf buf = (ByteBuf) msg;
                                String name = buf.toString();
                                // 传递给下一个数据
                                super.channelRead(ctx, name);
                            }
                        });

                        pipeline.addLast("h2", new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                System.out.println();
                                super.channelRead(ctx, msg);
                                // 向前去找出栈的处理器
                                ctx.writeAndFlush(msg);
                                // 向后去找出栈的处理器
                                ch.writeAndFlush(ctx.alloc().buffer().writeBytes("write".getBytes(StandardCharsets.UTF_8)));
                            }
                        });
                        // 只有写的时候才触发
                        pipeline.addLast("h3", new ChannelOutboundHandlerAdapter(){
                            @Override
                            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                System.out.println();
                                super.write(ctx, msg, promise);
                            }
                        });
                    }
                })
                .bind(8080);
    }
  • 快速测试
    public static void main(String[] args) {
        ChannelInboundHandlerAdapter h1 = new ChannelInboundHandlerAdapter(){
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                super.channelRead(ctx, msg);
            }
        };

        ChannelOutboundHandlerAdapter h2 = new ChannelOutboundHandlerAdapter(){
            @Override
            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                super.write(ctx, msg, promise);
            }
        };

        EmbeddedChannel channel = new EmbeddedChannel(h1, h2);
        // 入栈
        channel.writeInbound(ByteBufAllocator.DEFAULT.buffer().writeBytes("hello world".getBytes(StandardCharsets.UTF_8)));
        // 出栈
        channel.writeOutbound(ByteBufAllocator.DEFAULT.buffer().writeBytes("hello world".getBytes(StandardCharsets.UTF_8)));
    }

6.ByteBuf

池化的最大意义在于可以重用ByteBuf,优点有

  • 没有池化,则每次都得创建新的ByteBuf实例,这个操作对直接内存代价昂贵,就算是堆内存,也会增加GC压力
  • 有了池化,则可以重用池中ByteBuf实例,并且采用了与jemalloc类似的内存分配算法提升分配效率
  • 高并发时,池化功能更节约内存,减少内存溢出的可能
    池化功能是否开启,可以通过下面的系统环境变量来设置
    -Dio. netty. allocator . type=
  • 4.1以后,非Android平台默认启用池化实现,Android平台启用非池化实现
  • 4.1之前,池化功能还不成熟,默认是非池化实现

6. 组成

  • 容量
  • 最大容量
  • 读写指针
posted @ 2024-07-10 16:39  lwx_R  阅读(2)  评论(0编辑  收藏  举报