Java-第十七部分-NIO和Netty-核心模块组件

353 阅读5分钟

NIO和Netty全文

核心模块组件

ServerBootstrap和Bootstrap

  • 引导类,一个Netty应用由一个Bootstrap开始,负责配置整个Netty程序,串联各个组件
  1. ServerBootstrap服务端
  2. Bootstrap客户端
  • 链式编程,常见方法
  • 服务端
bootstrap.group(bossGroup, workerGroup) //设置两个group
        .channel(NioServerSocketChannel.class) //设置服务器端通道的实现
        .option(ChannelOption.SO_BACKLOG, 128) //设置线程队列等待连接的个数,设置服务端的通道
        .childOption(ChannelOption.SO_KEEPALIVE, true) //设置接收到的通信添加配置
        .handler(null) //针对bossGroup
        .childHandler(new HttpServerInitializer()); //设置业务处理类
        .bind(8888).sync(); //绑定端口,同步执行
  • 客户端
bootstrap.group(group) //设置线程组,客户端一个
        .channel(NioSocketChannel.class) //设置通道实现类
        .handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new ClientHandler()); //加入自定义处理器
            }
        })
        .connect("localhost", 8888).sync(); //连接服务器

Future和ChannelFuture

  • Netty所有IO操作都是异步的,不能立刻得到消息是否被正确处理,通过Future和ChannelFuture注册监听,操作成功或失败时会自动触发监听事件
  • 获取正在进行IO操作的通道
cf.channel()
  • 等待异步操作实行完毕,没完成就阻塞,变成同步
cf.sync()

Channel

  • 网络通信的组件,用于执行网络IO操作
  • 通过Channel获取网络通信的状态、配置参数
channel.remoteAddress();//远程地址
//还需要从缓存减去多少内容才能写
channel.bytesBeforeWritable(); //可写大小
channel.config(); //配置
  • 提供异步的网络IO操作连接、读写、绑定端口,通过ChannelFuture进行监听
  • 支持关联IO操作与对应的处理程序
  • 不同协议、不同阻塞类型的连接都有不同Channel与之对应
//异步TCP Socket
NioSocketChannel nioSocketChannel = new NioSocketChannel();
//异步服务端TCP Socket连接
NioServerSocketChannel nioServerSocketChannel = new NioServerSocketChannel();
//异步UDP连接
NioDatagramChannel nioDatagramChannel = new NioDatagramChannel();
//异步Sctp连接,Stream Control Transmission Protocol,流控制传输协议
//兼有TCP/UDP两者特征,以数据块为单位传输
NioSctpChannel nioSctpChannel = new NioSctpChannel();
//异步Sctp服务器端连接
NioSctpServerChannel nioSctpServerChannel = new NioSctpServerChannel();

Selector

  • Netty基于Selector实现IO多路复用,通过Selector一个线程可以监听多个Channel事件
  • 向一个Selector注册Channel后,Selector的内部机制自动不断地查询(select方法),查询注册的Channel是否有就绪的IO事件

ChannelHandler

  • 一个接口,处理IO事件,拦截IO事件,将其转发到其ChanneilPipeline中的下一个处理程序
  • 本身没有提供实现方法,重写其中的方法,可以监听channel和handler的各个状态 image.png
  • ChannelInBoundHandler处理入站IO事件,服务器往管道里写,客户端从管道读
  • ChannelOutBoundHandler处理出站IO事件,服务器从管道中读取数据,客户端往管道写
  • 适配器
  1. ChannelInBoundHandlerAdapter处理入站IO事件
  2. ChannelOutBoundHandlerAdapter处理出站IO事件
  3. ChannelDuplexHandler处理入站/出站IO事件 image.png

Pipeline和ChannelPipeline

  • 默认认为入栈为服务端向客户端端
  • ChannelPipelineHandler的集合,负责处理和拦截inbound/outbound的时间和操作,相当于一个贯穿Netty的工作链,实现了一种高级形式的拦截过滤器模式,用户完全控制事件的处理方式,以及工作链中的运行过程
  • ChannelChannelPipeline的组成关系
  1. 一个Channel和一个ChannelPipeline互相包含,ChannelPipeline维护余个由ChannelHandlerContext组成的双向链表,每个ChannelHandlerContext关联一个ChannelHandler
  2. 入站和出站事件在一个双向链表中,入栈从链表头到尾,出栈从链表尾到头,但是两种事件互不干扰
  • head为服务端,tail为客户端 image.png
  • 在链表中Handler的真正类型为DefaultChannelHandlerContext image.png
  • ChannelInitializer,实际上为inboundHandler,作为链表头第一个Handler image.png

ChannelHandlerContext

  • 保存Channel上下文信息,关联一个事件处理器ChannelHandler对象,绑定对应的pipeline/channel
  • ctx的真实类型为class io.netty.channel.DefaultChannelHandlerContext image.png
  • 获取信息
ctx.channel();
ctx.pipeline();
ctx.handler();
  • writeAdnFlush(Object msg)将数据写到ChannelPipeline当前ChannelHandler的下一个ChannelHandler,出站

ChannelOption

  • 参数设置 -ChannelOption.SO_BACKLOG,对应TCP/IP协议listen函数中的backlog参数,用来初始化服务器可连接队列大小,服务端处理客户端连接请求是顺序处理的,一时间只能处理一个客户端,多个客户端请求时,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog指定了该队列的大小
  • ChannelOption.SO_KEEPALIVE 保持连接活动状态

EventLoopGroup

  • 是一组EventLoop的抽象,为了利用多核CPU资源,有多个EventLoop同时工作,每个EventLoop维护一个Selector image.png
  • 通常一个服务器端口ServerSocketChannel对应一个Selector和一个EventLoop线程
  • BossEventLoop负责接受客户端连接,并将SocketChannel交给WorkerEventLoopGroup进行IO处理 image.png
  • BossGroupWorkGroupEventLoop数,超过WorkGroupEventLoop数会重复使用 image.png
  • 断开连接,关闭线程
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();

Unpooled

  • Netty提供的专门用来操作缓冲区的工具类,获取ByteBuf
//默认大小 256
ByteBuf buffer = Unpooled.buffer();
  • Netty中ByteBuf不需要进行flip,底层维护了readerIndex/writerIndex
  1. readerIndex下一个读取的位置
  2. writerIndex下一个写入的位置
ByteBuf byteBuf = Unpooled.buffer(10);
for (int i = 0; i < 10; i++) {
    byteBuf.writeByte(i);
}
for (int i = 0; i < byteBuf.capacity(); i++) {
    System.out.println(byteBuf.readByte());
}

image.png

  • readerIndex/writerIndex/capacitybuf分成了三个区域
  1. [0, readerIndex)读过的区域
  2. [readerIndex, writerIndex)可读的区域
  3. [writerIndex, capacity)可写的区域
ByteBuf byteBuf = Unpooled.copiedBuffer("hello 掘金", Charset.forName("utf-8"));
if (byteBuf.hasArray()) { //是否分配了一个数组
    byte[] bytes = byteBuf.array();
    //24
    System.out.println(bytes.length);
    //[104, 101, 108, 108, 111, 32, -26, -114, -104, -23, -121, -111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    System.out.println(Arrays.toString(bytes));
    //转换成String 会自动扩容,需要截取
    System.out.println(new String(bytes, 0, byteBuf.readableBytes(), Charset.forName("utf-8")));
    //UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 12, cap: 24)
    System.out.println(byteBuf);
    //0
    System.out.println(byteBuf.arrayOffset());
    //12
    System.out.println(byteBuf.writerIndex());
    //0
    System.out.println(byteBuf.readerIndex());
    //24
    System.out.println(byteBuf.capacity());
    //可读取字节数 12
    System.out.println(byteBuf.readableBytes());
    for (int i = 0; i < byteBuf.readableBytes(); i++) {
        System.out.println((char)byteBuf.getByte(i));
    }
    //hell 0开始读4个
    System.out.println(byteBuf.getCharSequence(0, 4, CharsetUtil.UTF_8));
    System.out.println(byteBuf.getCharSequence(3, 7, CharsetUtil.UTF_8));
}