Java NIO极简说明

快速回顾Java NIO相关的知识.

基础概念

Three key components of NIO are:

  • Channel
  • Buffer
  • Selector

Channel

All the data are transfer over channel which like a stream. Channel is bidirectional so it supports write and read.
There are servel typical channel:

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel
    And some typical buffer like:
  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer
  • MappedByteBuffer
    They have cover most of the basic data types.

Buffer

Buffer is essentially a specified piece of memory use for transfer data.
The function of buffer depends on three properties:

  • capacity
  • position
  • limit
    In one of two modes they have different meaning:
    Write Mode:
    capacity is the max that this buffer can contain.
    limit is the same as capacity in write mode.
    position is the current writing index. The max of position is (capacity-1).
    Read Mode:
    capacity is the max of this buffer.
    limit is the max amount of data you can read from a buffer.
    position is the current index while reading.

Selector

User thread register channels to selector and specified some event(connect, accept, read, write) that we care. Then use Selector.select() to check if there is any channel is ready for connect or accept or read or write.
In this way we can only use one thread to communicate with a large amount of channels. That is what NIO do for us.

基础使用

这里有完整的简单demo.
下面以socket为例,概括nio通信的主要步骤:

服务端启动步骤

// 1. 创建channel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);

// 2. 创建socket并将其设置到端口监听状态
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress(8080));

// 3. 创建selector
this.selector = Selector.open();;

// 4. 绑定channel的accept事件到selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

// 至此完成服务端的启动,下面开始处理服务端接受到的数据. 一般以下步骤会在一个循环中反复执行
... ... 


// 1. 等待selector有新事件发生, 注意selector一次可能接收到多个事件, 需要分别处理
while(selector.select()>0){
    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
    while(iterator.hasNext()){
        SelectionKey selectionKey = iterator.next();
        iterator.remove();
        
        // 2.1 isAcceptable() 判断是否有新的连接创建
        if(selectionKey.isAcceptable()){
            // 2.1.1 selectionKey.channel()方法获取创建此事件的channel, 
            // 这里我们只有ServerSocketChannel监听着ACCEPT事件,所以直接向下转型
            ServerSocketChannel server = (ServerSocketChannel)selectionKey.channel();

            // 2.1.2 新连接进来,从ServerSocketChannel创建(accept)一个SocketChannel
            SocketChannel socketChannel  = server.accept();
            if(socketChannel==null){
                continue;
            }
            socketChannel.configureBlocking(false);

            // 2.1.3 将SocketChannel的读写事件均注册到同一个selector, 实现单线程监听多路io
            socketChannel.register(selector, 
              SelectionKey.OP_READ| SelectionKey.OP_WRITE);
            
            // ... 下面可以处理读写
        }

        // 2.2 isReadable() 判断是否可读
        if(selectionKey.isReadable()){
            
            // 2.2.1 类似于2.1.1, 读写事件只有SocketChannel在监听,可以直接转型
            SocketChannel socketChannel = (SocketChannel)selectionKey.channel();

            // 2.2.2 操作buffer, 从channel读数据到buffer, 注意读取之后需要flip翻转读写状态
            readBuffer.clear();
            socketChannel.read(readBuffer);
            readBuffer.flip();
            String receiveData= Charset.forName("UTF-8")
              .decode(readBuffer).toString();
            
            // 2.2.3 把读到的数据绑定到key中, 底层是把数据赋值给key对象的一个字段, 
            // 多次调用attach会覆盖旧值. 之后key可以取出数据
            selectionKey.attach("server message echo:"+receiveData);
        }

        // 2.3 isWritable() 判断是否可写
        if(selectionKey.isWritable()){
        
            // 2.3.1 同2.1.1
            SocketChannel socketChannel = (SocketChannel)selectionKey.channel();

            // 2.3.2 操作buffer, 将buffer中的数据写入到channel, 
            // 同样写入结束后需要翻转读写状态
            writeBuffer.clear();
            writeBuffer.put("hello".getBytes());
            writeBuffer.flip();
            while(writeBuffer.hasRemaining()){
                socketChannel.write(writeBuffer);
            }
        }
    }
}

客户端启动步骤

// 1. 操作跟服务端2.1接受新连接的类似, 主要区别在于服务端通过
// ServerSocketChannel的accept方法创建新的SocketChannel, 客户端通过
// SocketChannel.open()创建,并使用connect连接到服务端.
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(),8080));
selector=Selector.open();
socketChannel.register(selector,SelectionKey.OP_READ | SelectionKey.OP_WRITE);

// 2. 读写操作跟服务端完全一致,不赘述
    

发表评论

电子邮件地址不会被公开。

4 × = 4