NIO的主要对象Buffer、Channel、Selector

Buffer

Buffer是一个对象,包含一些要写入或者读出的数据。
在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,也是写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。缓冲区实际上是一个数组,并提供了对数据结构化访问以及维护读写位置等信息。具体的缓存区有这些:ByteBuffe、CharBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。他们实现了相同的接口:Buffer。

Channel

定义

通道Channel接口的定义只有两个方法,判断通道是否打开和关闭通道。

public interface Channel extends Closeable {

    public boolean isOpen();

    public void close() throws IOException;

}

通道主要分为两大类,文件(File)通道和套接字(socket)通道;
涉及的类有FileChannel类和三个socket通道类:SocketChannel、ServerSocketChannel和DatagramChannel。

创建通道

FileChannel

FileChannel通道只能通过在一个打开的RandomAccessFile、FileInputStream或FileOutputStream对象上调用getChannel( )方法来获取,如下所示:

RandomAccessFile raf = new RandomAccessFile ("somefile", "r"); 
FileChannel fc = raf.getChannel( );

SocketChannel

SocketChannel sc = SocketChannel.open( ); 
sc.connect (new InetSocketAddress ("somehost", someport));

ServerSocketChannel

 ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(somelocalport));

DatagramChannel

DatagramChannel dc = DatagramChannel.open( );

使用通道

在使用通道的时候,我们通常都将通道的数据取出存入ByteBuffer对象或者从ByteBuffer对象中获取数据放入通道进行传输;
在使用通道的过程中,我们要注意通道是单向通道还是双向通道,单向通道只能读或写,而双向通道是可读可写的;
如果一个Channel类实现了ReadableByteChannel接口,则表示其是可读的,可以调用read()方法读取;
如果一个Channel类实现了WritableByteChannel接口,则表示其是可写的,可以调用write()方法写入;
如果一个Channel类同时实现了ReadableByteChannel接口和WritableByteChannel接口则为双向通道,如果只实现其中一个,则为单向通道;
如ByteChannel就是一个双向通道,实际上ByteChannel接口本身并不定义新的API方法,它是一个聚集了所继承的多个接口,并重新命名的便捷接口;

例子,展示了两个通道之间拷贝数据的过程:

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;

public class Main {

  public static void main(String[] args) throws IOException {
    ReadableByteChannel source = Channels.newChannel(System.in);
    WritableByteChannel dest = Channels.newChannel(System.out);
    channelCopy1(source, dest);
    // channelCopy2 (source, dest);
    source.close();
    dest.close();

  }

  private static void channelCopy1(ReadableByteChannel src, WritableByteChannel dest)
  throws IOException {
    ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
    while (src.read(buffer) != -1) {
      // 切换为读状态
      buffer.flip();
      // 不能保证全部写入
      dest.write(buffer);
      // 释放已读数据的空间,等待数据写入
      buffer.compact();
    }
    // 退出循环的时候,由于调用的是compact方法,缓冲区中可能还有数据
    // 需要进一步读取
    buffer.flip();
    while (buffer.hasRemaining()) {
      dest.write(buffer);
    }
  }

  private static void channelCopy2(ReadableByteChannel src, WritableByteChannel dest)
  throws IOException {
    ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
    while (src.read(buffer) != -1) {
      // 切换为读状态
      buffer.flip();
      // 保证缓冲区的数据全部写入
      while (buffer.hasRemaining()) {
        dest.write(buffer);
      }
      // 清除缓冲区
      buffer.clear();
    }
    // 退出循环的时候,由于调用的是clear方法,缓冲区中已经没有数据,不需要进一步处理
  }

}

关闭通道

我们可以通过调用close()方法来关闭通道;
一个打开的通道代表与一个特定I/O服务的特定连接,并封装该连接的状态。当通道关闭时,这个连接会丢失,然后通道将不再连接任何东西。
可以通过isOpen()方法来判断通道是否打开,如果对关闭的通道进行读写等操作,会导致ClosedChannelException异常;
另外,如果一个通道实现了InterruptibleChannel接口,那么,当该通道上的线程被中断时,通道会被关闭,且该线程会抛出ClosedByInterruptException异常。

Selector

定义

Selector是Java NIO 编程的基础。Selector提供选择已经就绪的任务的能力:Selector会不断轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。所以,只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。

示意图

在这里插入图片描述

示例

服务端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
* NIO服务端
*
*/
public class NIOServer {
  //通道管理器
  private Selector selector;

  /**
   * 获得一个ServerSocket通道,并对该通道做一些初始化的工作
   * @param port  绑定的端口号
   * @throws IOException
   */
  public void initServer(int port) throws IOException {
    // 获得一个ServerSocket通道
    ServerSocketChannel serverChannel = ServerSocketChannel.open();
    // 设置通道为非阻塞
    serverChannel.configureBlocking(false);
    // 将该通道对应的ServerSocket绑定到port端口
    serverChannel.socket().bind(new InetSocketAddress(port));
    // 获得一个通道管理器
    this.selector = Selector.open();
    //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,
    //当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。
    serverChannel.register(selector, SelectionKey.OP_ACCEPT);
  }

  /**
   * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
   * @throws IOException
   */
  @SuppressWarnings("unchecked")
  public void listen() throws IOException {
    System.out.println("服务端启动成功!");
    // 轮询访问selector
    while (true) {
      //当注册的事件到达时,方法返回;否则,该方法会一直阻塞
      selector.select();
      // 获得selector中选中的项的迭代器,选中的项为注册的事件
      Iterator ite = this.selector.selectedKeys().iterator();
      while (ite.hasNext()) {
        SelectionKey key = (SelectionKey) ite.next();
        // 删除已选的key,以防重复处理
        ite.remove();
        // 客户端请求连接事件
        if (key.isAcceptable()) {
          ServerSocketChannel server = (ServerSocketChannel) key
                                       .channel();
          // 获得和客户端连接的通道
          SocketChannel channel = server.accept();
          // 设置成非阻塞
          channel.configureBlocking(false);

          //在这里可以给客户端发送信息哦
          channel.write(ByteBuffer.wrap(new String("向客户端发送了一条信息").getBytes()));
          //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。
          channel.register(this.selector, SelectionKey.OP_READ);

          // 获得了可读的事件
        } else if (key.isReadable()) {
          read(key);
        }

      }

    }
  }
  /**
   * 处理读取客户端发来的信息 的事件
   * @param key
   * @throws IOException
   */
  public void read(SelectionKey key) throws IOException {
    // 服务器可读取消息:得到事件发生的Socket通道
    SocketChannel channel = (SocketChannel) key.channel();
    // 创建读取的缓冲区
    ByteBuffer buffer = ByteBuffer.allocate(10);
    channel.read(buffer);
    byte[] data = buffer.array();
    String msg = new String(data).trim();
    System.out.println("服务端收到信息:"+msg);
    ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
    channel.write(outBuffer);// 将消息回送给客户端
  }

  /**
   * 启动服务端测试
   * @throws IOException
    */
  public static void main(String[] args) throws IOException {
    NIOServer server = new NIOServer();
    server.initServer(8000);
    server.listen();
  }

}

客户端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
* NIO客户端
*
*/
public class NIOClient {
  //通道管理器
  private Selector selector;

  /**
   * 获得一个Socket通道,并对该通道做一些初始化的工作
   * @param ip 连接的服务器的ip
   * @param port  连接的服务器的端口号
   * @throws IOException
   */
  public void initClient(String ip,int port) throws IOException {
    // 获得一个Socket通道
    SocketChannel channel = SocketChannel.open();
    // 设置通道为非阻塞
    channel.configureBlocking(false);
    // 获得一个通道管理器
    this.selector = Selector.open();

    // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调
    //用channel.finishConnect();才能完成连接
    channel.connect(new InetSocketAddress(ip,port));
    //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。
    channel.register(selector, SelectionKey.OP_CONNECT);
  }

  /**
   * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
   * @throws IOException
   */
  @SuppressWarnings("unchecked")
  public void listen() throws IOException {
    // 轮询访问selector
    while (true) {
      selector.select();
      // 获得selector中选中的项的迭代器
      Iterator ite = this.selector.selectedKeys().iterator();
      while (ite.hasNext()) {
        SelectionKey key = (SelectionKey) ite.next();
        // 删除已选的key,以防重复处理
        ite.remove();
        // 连接事件发生
        if (key.isConnectable()) {
          SocketChannel channel = (SocketChannel) key
                                  .channel();
          // 如果正在连接,则完成连接
          if(channel.isConnectionPending()) {
            channel.finishConnect();

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

          //在这里可以给服务端发送信息哦
          channel.write(ByteBuffer.wrap(new String("向服务端发送了一条信息").getBytes()));
          //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。
          channel.register(this.selector, SelectionKey.OP_READ);

          // 获得了可读的事件
        } else if (key.isReadable()) {
          read(key);
        }

      }

    }
  }
  /**
   * 处理读取服务端发来的信息 的事件
   * @param key
   * @throws IOException
   */
  public void read(SelectionKey key) throws IOException {
    //和服务端的read方法一样
  }


  /**
   * 启动客户端测试
   * @throws IOException
   */
  public static void main(String[] args) throws IOException {
    NIOClient client = new NIOClient();
    client.initClient("localhost",8000);
    client.listen();
  }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值