NIO 之 Channel实现原理

相关文章

BIO、NIO、AIO 内部原理分析
NIO 之 Selector实现原理
NIO 之 ByteBuffer实现原理数组

NIO概述

Java NIO 由如下几个核心部分组成:服务器

  • Channels
  • Buffers
  • Selectors

在传统IO中,流是基于字节的方式进行读写的。
在NIO中,使用通道(Channel)基于缓冲区数据块的读写。网络

Channel 和 IO 流的区别

Java NIO的通道相似IO中的流,但又有些不一样:异步

  1. 既能够从通道中读取数据,又能够写数据到通道。但流的读写一般是单向的。
  2. 通道能够异步地读写。
  3. 通道中的数据老是要先读到一个Buffer,或者老是要从一个Buffer中写入。

Channel 实现类

下面是JAVA NIO中的一些主要Channel的实现:socket

  1. FileChannel
    FileChannel 从文件中读写数据。
  2. DatagramChannel
    DatagramChannel 能经过UDP读写网络中的数据。
  3. SocketChannel
    SocketChannel 能经过TCP读写网络中的数据。
  4. ServerSocketChannel
    ServerSocketChannel能够监听新进来的TCP链接,像Web服务器那样。对每个新进来的链接都会建立一个SocketChannel。

FileChannel 是基于文件的Channel。
DatagramChannel、SocketChannel、ServerSocketChannel 都是基于网络流的Channel。编码

下面咱们主要介绍关于基于网络流的SocketChannel 和 ServerSocketChannel 。spa

register 方法

基于网络流的Channel中提供了register方法。
使用示例:.net

channel.register(selector,  Selectionkey.OP_READ, ByteBuffer.allocate(64));

代码的写法感受是要将selector对象注册到channel中。其实正好相反,应该是将channel注册到selector中。
下面经过代码分析:
![将channel 注册到 selector 中]code

channel 的 register 方法server

1.首先判断 selector 中是否已经注册此 channel ,若是注册过就更新注册的事件和attach附件信息。

  1. 若是 selector 没有注册过 channel ,则将 channel 注册到 selector 中。

configureBlocking 方法

public final SelectableChannel configureBlocking(boolean block)

Channel 默认使用阻塞模式,经过 configureBlocking 方法能够将该 Channel 设置成非阻塞模式。

//设置成非阻塞模式
channel.configureBlocking(false);

ServerSocketChannel

ServerSocketChannel只支持入站链接请求。不提供读取、写入数据功能。

accept 方法

public abstract SocketChannel accept()

accept()能够在阻塞或非阻塞模式下操做。

  • 阻塞模式
    accept()方法等待入站链接。而后它接受一个链接,并返回到远程客户端的一个SocketChannel。在创建链接前你没法作任何操做。

示例:

public static void blockMode(){
    try {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        //绑定要监控的端口
        serverChannel.bind(new InetSocketAddress(9000));
        //获取入站请求socketChannel
        SocketChannel clientChannel = serverChannel.accept();
        ...
    } catch (Exception e) {
    }
}
  • 非阻塞模式
    若是没有入站链接,accept()返回null。非阻塞模式通常和Selector结合使用。

示例:

public static void nonBlockMode(){
    try {
        //建立一个selector
        Selector selector = Selector.open();
        //建立一个ServerSocketChannel
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        //设置成非阻塞模式
        serverChannel.configureBlocking(false);
        //绑定要监控的端口
        serverChannel.bind(new InetSocketAddress(9000));
        //接受入站请求
        serverChannel.accept();
        //将 serverChannel 注册到 selector 上
        serverChannel.register(selector, SelectionKey.OP_READ);
    } catch (Exception e) {
    }
}

SocketChannel

SocketChannel 类能够读写TCP Socket。数据必须编码到ByteBuffer对象中来完成读写。

open 方法

  1. public static SocketChannel open()
    建立一个SocketChannel,但不链接(没有指定目标ip和port),若是想使用非阻塞模式则使用该方法建立SocketChannel对象。

示例:

public static void nonSocketChannel() throws Exception {
    SocketChannel client = SocketChannel.open();
    client.configureBlocking(false);
    client.connet(new InetSocketAddress("192.168.1.10", 9000));
}
  1. public static SocketChannel open(SocketAddress remote)
    构造SocketChannel 对象,并使用阻塞模式链接目标地址。

connect 方法

public abstract boolean connect(SocketAddress remote)

非阻塞模式下,若是要链接远程服务器必须使用 connect 方法进行链接。

finishConnect 方法

public abstract boolean finishConnect()

阻塞模式:直接返回true,由于在构造SocketChannel的时候已经创建链接了。
非阻塞模式下:必须调用此方法来判断该方法是否已经创建网络链接,只有创建网络链接后才能进行读写操做。

判断网络链接状态

public abstract boolean isConnected()
判断是否已经链接。
public abstract boolean isConnectionPending()
判断链接是否正在链接状态

read 方法

  • public int read(ByteBuffer buf)
    将 SocketChannel 中的数据读入填充到 buf 中
  • public final long read(ByteBuffer[] dsts)
    将 SocketChannel 中的数据读入填充到 dsts[] 数组。按照数组顺序进行填充。
  • public long read(ByteBuffer[] dsts, int offset, int length)
    将 SocketChannel 中的数据填充到 dsts[] 数组中,从第数组中的offset坐标开始填充,填充 length 个Bytebuffer。

write 方法

  • public int write(ByteBuffer src)
    将 buf 中的数据写入到 SocketChannel 中
  • public final long write(ByteBuffer[] srcs)
    将 srcs[] 数组中的数据写入到 SocketChannel 中,按照数组顺序一个一个写入。
  • public long write(ByteBuffer[] srcs, int offset, int length) 将 srcs[] 数组中的数据写入到 SocketChannel 中,从数组中 offset 坐标开始,写入length个ByteBuffer对象。