netty之NIO入门

NIO核心API Channel, Buffer, Selector

3大核心组件
  Selector(选择器):
      Selector根据通道的事件,选择给哪个通道服务;
 
  Channel(通道): 类似于bio中的socket
    数据通道;
 
  Buffer(缓冲区):
      每一个通道对应一个Buffer缓冲区;
      通道和Buffer之间是双向的,Buffer和通道之间可以相互读写;
 

 

通道Channel

NIO的通道类似于流,但有些区别如下:

1. 通道可以同时进行读写,而流只能读或者只能写

2. 通道可以实现异步读写数据

3. 通道可以从缓冲读数据,也可以写数据到缓冲: 

 

缓存Buffer

缓冲区本质上是一个可以写入数据的内存块,然后可以再次读取,该对象提供了一组方法,可以更轻松地使用内存块,使用缓冲区读取和写入数据通常遵循以下四个步骤:

1. 写数据到缓冲区;

2. 调用buffer.flip()方法;

3. 从缓冲区中读取数据;

4. 调用buffer.clear()或buffer.compat()方法;

当向buffer写入数据时,buffer会记录下写了多少数据,一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式,在读模式下可以读取之前写入到buffer的所有数据,一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。

Buffer在与Channel交互时,需要一些标志:

buffer的大小/容量 - Capacity

作为一个内存块,Buffer有一个固定的大小值,用参数capacity表示。

当前读/写的位置 - Position​

当写数据到缓冲时,position表示当前待写入的位置,position最大可为capacity – 1;当从缓冲读取数据时,position表示从当前位置读取。

信息末尾的位置 - limit

在写模式下,缓冲区的limit表示你最多能往Buffer里写多少数据; 写模式下,limit等于Buffer的capacity,意味着你还能从缓冲区获取多少数据。

下图展示了buffer中三个关键属性capacity,position以及limit在读写模式中的说明:

缓冲区常用的操作

向缓冲区写数据:

    1. 从Channel写到Buffer;

    2. 通过Buffer的put方法写到Buffer中;

从缓冲区读取数据:

    1. 从Buffer中读取数据到Channel;

    2. 通过Buffer的get方法从Buffer中读取数据;

flip方法:

     将Buffer从写模式切换到读模式,将position值重置为0,limit的值设置为之前position的值;

clear方法 vs compact方法:

       clear方法清空缓冲区;compact方法只会清空已读取的数据,而还未读取的数据继续保存在Buffer中;

Selector

一个组件,可以检测多个NIO channel,看看读或者写事件是否就绪。

多个Channel以事件的方式可以注册到同一个Selector,从而达到用一个线程处理多个请求成为可能。

 



 
  Client(Socket):
    程序不会直接读写Channel,是往Buffer里面读写,让通道和Buffer之间有个缓冲;
    底层通过Buffer实现非阻塞,NIO就是面向缓冲区的,面向块的;
 
    InActive
 
    Active
 
      每个通道发生了关心的事情(读、写、连接),那么就去处理;
      还可以处理其它任务;
      这是通过后台运行一个线程来进行处理的;
 
 
3)Selector、Channel、Buffer的关系:
    (1)每个Channel对应一个Buffer;
    (2)Selector对应一个线程,一个线程对应多个Channel(连接/传统java的一个Stream);
        有多个Channel注册到了这个Selector上:
            到底是哪个Channel在工作;
    (3)程序切换到哪个Channel是由事件决定的,Event是一个非常重要的概念;
    (4)Selector会根据不同的事件,在各个通道上切换;
    (5)Buffer就是一个内存块(NIO就是面向块/Buffer编程的),底层是有一个数组的;
    (6)数据的读取和写入是通过Buffer,这个是和BIO, BIO中要么是输入流,要么是输出流,不能双向;
        但是NIO的Buffer是可以读和可以写的,但是需要flip进行切换;
    (7)Channel也是双向的,可以反映底层操作系统的情况;
        比如:Linux底层的操作系统的通道就是双向的;
        Channel是和Buffer是对应的;
 
    根据Channel上发生的事件,进行事情的处理;
 
4)缓冲区(Buffer)
    程序不能直接从Channel读写数据,必须从Buffer缓冲区读写;
    Buffer是顶级抽象类,有7个子类,没有boolean: Byte,Short,Char,Int,Long,Double,Float;
      用的最多的是ByteBuffer,因为其它的也要最终转化为字节;
 
    4个重要属性:
        int mark = -1;
            标记:一旦flip后就作废了;
 
        int position = 0;
            位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变该值,并为下次读写做准备;
 
        int limit;
            表示缓冲区的当前终点,不能对缓冲区超过的极限的位置进行读写操作,且极限是可以修改的;
              之所以修改是因为:Buffer是可读可写的;
 
        int capacity; 
            容量,既可以容纳的最大数据量,在缓冲区创建的时候被设定,并且不能改变;
 
    断点一下一目了然;
 
    ByteBuffer
          (1)网络数据在底层都是字节形式传输的;
 
           (2)put什么类型的数据,get也采用取什么类型的数据,不然可能有异常
 
5)Channel(通道)
    (1)nio的通道类似于流,但是有区别:
      通道可以同时进行读写,而流只能读或者写;
      通道可以实现异步读写数据;
      通道可以从缓冲读数据,也可以写数据到缓冲;
 
    (2)4个重要常用Channel类:
      FileChannel: 用于文件的数据读写
 
      DatagramChannel: 用于UDP的数据读写
 
      ServerSocketChannel(用于TCP数据的读写):
          当客户端请求连接的时候,ServerSocketChannel,会生成一个对应的SocketChanel,与服务器通信;
 
      SocketChannel(用于TCP数据的读写)
 
    (3)继承关系
      Channel-->NetworkChannel-->
          ServerSocketChannel-->ServerSocketChannelImpl(真正类型)
          SocketChannel-->SocketChannelImpl(真正类型)
 
    (4)FileChannel写数据例子
      "xxxxx"字符串-->ByteBuffer-->java输出流对象包含了NIOFileChannel-->文件:
           是FileOutputStream内置了一个通道Channel,这个可以打断点可以知道;
 
      flip的作用: limit设置为真实的大小;
 
    (5)字节不可以直接显示,要转为字符串;
         底层的hb数组
         byteBuffer,array()
         记得最后要关闭流
 
    (6)文件拷贝
        不知道数据有多大,需要用while循环读取;
        流和文件关联起来;
 
 6)Selector(选择器)
   Selector上面能够注册通道;
 
   Selector不停的在轮训哪个Channel上面有事件发生;
 
   通过缓冲Buffer实现非阻塞;
 
   一旦检测到有哪个事件发生了,调用select就会拿到SelectionKey的集合,然后就可以反向得到Channel,也知道是读、写、连接
     等哪个事件发生了;
 
   select(): 直到注册的Channel里面至少有一个事件发生,才会返回,否则一直阻塞;
 
   select(2000): 最多等待2s,及时没有事件也会返回; 
 
   wakeup(): 即使正在阻塞,调用后立马返回
 
   selectNow(): 不阻塞,立马返回
 
   NIO非阻塞原理:
       1.当客户端连接时,会通过ServerSocketChannel得到SocketChannel;
       2.将SocketChannel注册到Selector上,register(Selector sel, int opts),一个Selector上面可以注册多个Channel;
       3.注册后返回一个SelectionKey,会和该Selector关联(集合);
       4.Selector进行监听,select方法会返回由事件发生的通道的个数;
       5.进一步得到SelectionKey(有事件发生);
       6.在通过SelectionKey反向获取Channel;
       7.可以通过得到的Channel,完成业务的处理;
 
       本身accept是阻塞的,但是由于是事件驱动,确切已经知道发生了连接事件了,因此accept一定是立即返回的;
 
    给Channel关联一个Buffer:
           socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
 
           OP_READ: 就是客户端发来了消息
 
 
  ServerSocketChannel: 监听新的客户端连接,生成SocketChannel完事;
  SocketChannel: 负责具体的读写操作;
 
7)零拷贝(网络编程优化)
  内存拷贝:
     用户态和内核态的切换次数来衡量:
 
  DMA:
    Direct Memory Access(直接内存拷贝 不经过CPU)
 
  (1)2种零拷贝的方法:
    1:mmap(内存映射 3次拷贝,3次状态的切换);
 
      传统IO read:(4次拷贝,3次状态的切换) 
       先通过DMA,把数据从硬盘拷贝到内核Buffer, 
       然后把数据从内核Buffer用CPU拷贝到用户Buffer,
       用户数据在用户Buffer修改完毕以后,再从用户Buffer使用CPU拷贝到Socket Buffer,
       DMA拷贝到协议栈
     
      优化:将文件映射到内核缓冲区,同时用户控件可以共享内核的数据,这样在网络传输时,就可以减少内核空间
        到用户空间的拷贝次数;
 
        硬件拷贝到内核缓冲;
        user buffer和kernel buffer可以共享数据,这样减少了一次cpu拷贝,这样就在内核缓存进行修改,cpu拷贝到socket buffer;
        socket buffer进行DMA拷贝到协议栈;
 
      总结:mmap并不是真正的零拷贝,但是确实是少了一次拷贝,因为内核缓冲和user缓冲可以共用数据;
 
    2:sendFile;
      linux 2.1
        数据根本不经过用户态,直接从内核缓冲区到socket buffer,由于和用户态完全无关,就减少了一次上下文切换;
 
        硬件DMA拷贝到kernel buffer
        kernel buffer进行一次cpu拷贝到socket buffer
        socket buffer拷贝到协议栈
 
        4-->3 拷贝
        3-->2 切换
        3次上下文切换变为2次
        少了一次cpu
 
        总结: 
          DMA拷贝是少不了的,必须存在硬件拷贝到kernel buffer,这是少不了的,因此零拷贝不是没有拷贝,而是指的没有CPU拷贝;
 
      linux 2.4:
        DMA
 
        kernel buffer -->socket buffer: DMA拷贝到协议栈  
          kernel buffer需要很少数据拷贝到协议栈: 长度 offset
 
        还是有一次CPU拷贝的,但是拷贝的信息很少,消耗很低,可以忽略(描述信息的拷贝);
 
        4-->2拷贝:  hard disk-->kernel buffer-->协议栈
        3-->2上下文切换:
 
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值