一、NIO是什么?
NIO的全称是New I/O,与之相对应的是Java中传统的I/O,这里都指的是Java的API包。
传统的IO包提供的是同步阻塞IO,即当用户线程发出IO请求后,内核会去查看数据是否已经就绪,若未就绪,则用户线程会处于阻塞状态(让出CPU),当数据就绪后,内核会将数据复制到用户线程,并把结果返回给用户线程,同时接触用户线程的阻塞,同步体现在用户线程需要等待数据就绪后才能向后执行(后面的执行依赖于前面的结果)。服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,线程数量也会受到。
而NIO包提供的IO是同步非阻塞IO,非阻塞体现在用户线程发起IO请求后,会直接得到返回结果,即便在数据未就绪的情况下,也能马上得到失败信息。而同步体现在用户线程需要主动去轮询直到发现数据就绪,再主动将数据从内核拷贝到用户线程。服务器实现模式为多个连接一个线程(IO多路复用),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
传统IO模型 | NIO | |
客户端个数IO线程 | 1:1 | M:1(1个IO线程处理多个客户端连接) |
IO类型(阻塞) | 阻塞IO | 非阻塞IO |
IO类型(同步) | 同步IO | 同步IO(IO多路复用) |
API使用难度 | 简单 | 非常复杂 |
调式难度 | 简单 | 复杂 |
可靠性 | 非常差 | 高 |
吞吐量 | 低 | 高 |
二、NIO包API的用法。
(1)Channel:Java NIO中的所有I/O操作都基于Channel对象,一个Channel(通道)代表和某一实体的连接,这个实体可以是文件、网络套接字等。也就是说,通道是Java NIO提供的一座桥梁,用于我们的程序和操作系统底层I/O服务进行交互。
FileChannel:文件通道,从输入流输出流中获取实例,常见的使用场景就是从一个文件拷贝其内容到另一个文件。
- FileInputStream fis = new FileInputStream(“E://zfb.txt”);
- FileChannel ifc = fis.getChannel();
- FileOutputStream os = new FileOutputStream(“E://zfb2.txt”);
- FileChannel ofc = os.getChannel();
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- while (ifc.read(buffer) != -1){
- System.out.println(1);
- buffer.flip();
- ofc.write(buffer);
- buffer.clear();
- }
SocketChannel/ServerSocketChannel:套接字通道,通过静态方法获取实例,使用场景在最后会给出demo。
SocketChannel socketChannel = SocketChannel.open();
(2)Buffer:NIO中所使用的缓冲区不是一个简单的byte数组,而是封装过的Buffer类,通过它提供的API,我们可以灵活的操纵数据。与Java基本类型相对应,NIO提供了多种 Buffer 类型,如ByteBuffer、CharBuffer、IntBuffer等,区别就是读写缓冲区时的单位长度不一样(以对应类型的变量为单位进行读写)。Buffer中有3个很重要的变量,它们是理解Buffer工作机制的关键,分别是capacity (总容量)、position (指针当前位置)、limit (读/写边界位置)。
接下来看三个方法flip、rewind、clear
- public final Buffer flip() {//用于写模式到读模式的转换
- limit = position;//设置上届为当前位置
- position = 0;//当前位置设置为0
- mark = -1;//重置写标记
- return this;
- }
- public final Buffer rewind() {//设置当前位置为0
- position = 0;
- mark = -1;
- return this;
- }
- public final Buffer clear() {//通过指针移动的方式清空缓冲区
- position = 0;//设置当前位置为0
- limit = capacity;//上限为容量
- mark = -1;
- return this;
- }
(3)Selector:Selector(选择器)是一个特殊的组件,用于采集各个通道的状态(或者说事件)。我们先将通道注册到选择器,并设置关心的事件,然后就可以通过调用select()方法,监听关注事件的发生。通道有4个事件可供我们监听:Accept、Connect、Read、Write。
selector.select()方法是阻塞的方法!!!若注册的通道没有事件到达,则不会向下执行。但是NIO的IO机制是非阻塞的!!!
三、NIO的编程模型——基于Reactor模式的事件驱动。
NIO是同步非阻塞的,但是提供了基于Selector的异步网络IO。这句话的意思是NIO的IO机制是同步非阻塞的,而基于Selector这个组件的编程模型(Reactor)是事件驱动的,是异步的。
下面来看一下一般采用NIO的编程模型:
demo服务端代码:
- public class NIOServer {
-
- private final static int BUFFER_SIZE = 1024;
-
- private final static int PORT = 52621;
-
- private ServerSocketChannel serverSocketChannel;
-
- private ByteBuffer byteBuffer;
-
- private Selector selector;
-
- public NIOServer(){
- try {
- init();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- public void init() throws IOException {//初始化
- serverSocketChannel = ServerSocketChannel.open();
- serverSocketChannel.configureBlocking(false);
- serverSocketChannel.bind(new InetSocketAddress(PORT));
- selector = Selector.open();
- serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
- byteBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
-
- }
-
- private void listen() throws IOException {
- while (true){
- if (selector.select() == 0){
- continue;
- }
- Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
- while (iterator.hasNext()){
- SelectionKey key = iterator.next();
- if (key.isAcceptable()){
- ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
- SocketChannel socketChannel = ssc.accept();
- socketChannel.configureBlocking(false);
- socketChannel.register(selector, SelectionKey.OP_READ);
- replyClient(socketChannel);
- }
- if (key.isReadable()){
- readDataFromClient(key);
- }
- iterator.remove();
- }
- }
- }
-
- private void readDataFromClient(SelectionKey key) throws IOException {
- SocketChannel socketChannel = (SocketChannel) key.channel();
- byteBuffer.clear();
- int n;
- while ((n = socketChannel.read(byteBuffer)) > 0){
- byteBuffer.flip();
- while (byteBuffer.hasRemaining()){
- socketChannel.write(byteBuffer);
- }
- byteBuffer.clear();
- }
- if (n < 0){
- socketChannel.close();
- }
- }
-
- private void replyClient(SocketChannel channel) throws IOException {
- byteBuffer.clear();
- byteBuffer.put("Hello,I am server!".getBytes());
- byteBuffer.flip();
- channel.write(byteBuffer);
- }
-
- public static void main(String[] args) {
- try {
- new NIOServer().listen();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
客户端代码:
- public class NIOClient {
-
- private final static int BUFFER_SIZE = 1024;
-
- private final static int PORT = 52621;
-
- private SocketChannel socketChannel;
-
- private ByteBuffer byteBuffer;
-
- public void connect() throws IOException {
- socketChannel = SocketChannel.open();
- socketChannel.connect(new InetSocketAddress("127.0.0.1", PORT));
- byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
- receive();
- }
-
- private void receive() throws IOException {
- while (true){
- int n;
- byteBuffer.clear();
- while ((n = socketChannel.read(byteBuffer)) > 0){
- byteBuffer.flip();
- System.out.println("从服务器收到消息: " + new String(byteBuffer.array()));
- //socketChannel.write(byteBuffer);//发消息
- byteBuffer.clear();
- }
- }
- }
-
- public static void main(String[] args) {
- try {
- new NIOClient().connect();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }