NIO的实现原理

Jetty、Mina、Netty、ZooKeeper等都是基于NIO方式实现。

一、通道(Channel

1.通道是对原 I/O 包中的流的模拟,所有数据都必须通过通道。它是一个对象,可以通过它读取和写入数据。借助通道可以用最小的总开销来访问操作系统本身的 I/O 服务。

2.主要类型

分别对应文件IO、UDP和TCP(Server和Client)。

  • FileChannel

  • DatagramChannel

  • SocketChannel

  • ServerSocketChannel

3.通道可以以阻塞(blocking)或非阻塞(nonblocking)模式运行

二、缓冲区(Buffer

1.缓冲区:实质上是一个容器,一个连续的数组(通常指字节数组)。所有数据都通过 Buffer 对象来处理,写入到通道的所有对象都必须先放到缓冲区中;同样地,从通道中读取的任何数据都要先读到缓冲区中。nio其实就是利用缓冲区传输字节。

2.缓冲区类型

ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer

3.buffer的工作机制

apacity 缓冲区数组的总长度
position 下一个要操作的数据元素的位置
limit 缓冲区数组中不可操作的下一个元素的位置,limit<=capacity
mark 用于记录当前 position 的前一个位置或者默认是 0

(1)初始化buffer数组;

(2)向buffer数组开始写入几个字节的时候,position会移动到数据结束的下一个位置,这时候需要把buffer中的数据写到channel管道中,这时候会调用buffer.flip()方法;

注:buffer.flip()的作用:就会把buffer的当前位置更改为buffer缓冲区的第一个位置,设置为limit。

flip方法涉及到buffer中的Capacity,Position和Limit三个概念:

其中Capacity表示缓冲区大小,在读写模式下都是固定的;

Position类似于读写指针,表示当前读(写)到什么位置;

limit在读模式下表示最多能读多少数据(缓存中的实际数据大小相同),在写模式下表示最多能写入多少数据(和Capacity相同);

(3)调用buffer.flip()后读写指针指到缓存头部,并且设置了最多只能读出之前写入的数据长度(而不是整个缓存的容量大小)。如果不调用flip()方法,就是从文件最后开始读取的,什么也读不到。

(4)再下一次写数据之前,调用clear()方法,缓冲区的索引位置又回到初始位置。(这一步有点像IO中的把转运字节数组 char[] buf = new char[1024]; 不足1024字节的部分给强制刷新出去)

package ali.java.nio.ibm;     
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
 
public class CopyFile {
static public void main(String args[]) throws Exception {

    String infile = "c://test/nio_copy.txt";
    String outfile = "c://test/result.txt";

    FileInputStream fin = new FileInputStream(infile);
    FileOutputStream fout = new FileOutputStream(outfile);
    // 获取读的通道
    FileChannel fread = fin.getChannel();
    // 获取写的通道
    FileChannel fwrite = fout.getChannel();
    // 定义缓冲区,并指定大小
    ByteBuffer buffer = ByteBuffer.allocate(1024);

    while (true) {
        // 清空缓冲区
        buffer.clear();
        //从通道读取一个数据到缓冲区
        int r = fread.read(buffer);
        //判断是否有从通道读到数据
        if (r == -1) {
            break;
        }
        //将buffer指针指向头部
        buffer.flip();
        //把缓冲区数据写入通道
        fwrite.write(buffer);
    }
  }
}

三、选择器(Selectors):通过一个线程管理多个通道。

 

1.为了实现Selector管理多个SocketChannel,必须将具体的SocketChannel对象注册到Selector,并声明需要监听的事件(4种)

(1)connect:客户端连接服务端事件,对应值为SelectionKey.OP_CONNECT(8)
(2)accept:服务端接收客户端连接事件,对应值为SelectionKey.OP_ACCEPT(16)
(3)read:读事件,对应值为SelectionKey.OP_READ(1)
(4)write:写事件,对应值为SelectionKey.OP_WRITE(4)

2.服务端代码

ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(port));
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while(true){
    int n = selector.select();
    if (n == 0) continue;
    Iterator ite = this.selector.selectedKeys().iterator();
    while(ite.hasNext()){
        SelectionKey key = (SelectionKey)ite.next();
        if (key.isAcceptable()){
            SocketChannel clntChan = ((ServerSocketChannel) key.channel()).accept();
            clntChan.configureBlocking(false);
            //将选择器注册到连接到的客户端信道,
            //并指定该信道key值的属性为OP_READ,
            //同时为该信道指定关联的附件
            clntChan.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufSize));
        }
        if (key.isReadable()){
            handleRead(key);
        }
        if (key.isWritable() && key.isValid()){
            handleWrite(key);
        }
        if (key.isConnectable()){
            System.out.println("isConnectable = true");
        }
      ite.remove();
    }
}

//注释

(1)创建ServerSocketChannel实例,并绑定指定端口;
(2)创建Selector实例;

(3)将serverSocketChannel注册到selector,并指定事件OP_ACCEPT,其中最底层的socket通过channel和selector建立关联;

(4)如果没有准备好的socket,select方法会被阻塞一段时间并返回0;

(5)如果底层有socket已经准备好,selector的select方法会返回socket的个数,而且selectedKeys方法会返回socket对应的事件(connect、accept、read or write);
(6)根据事件类型,进行不同的处理逻辑;

在步骤3中,selector只注册了serverSocketChannel的OP_ACCEPT事件
1、如果有客户端A连接服务,执行select方法时,可以通过serverSocketChannel获取客户端A的socketChannel,并在selector上注册socketChannel的OP_READ事件。
2、如果客户端A发送数据,会触发read事件,这样下次轮询调用select方法时,就能通过socketChannel读取数据,同时在selector上注册该socketChannel的OP_WRITE事件,实现服务器往客户端写数据。

//TODO

结合netty来看,暂时到这

3.Selector底层原理、Selector是如何做到同时管理多个socket?

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值