什么还不懂Java NIO,看这一篇博客就够了!

Java NIO 编程

1.1 Java NIO 基本介绍
  1. Java NIO 全称 java non-blocking IO,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改进的输入/输出的新特性,被统称为 NIO(即 New IO),是同步非阻塞的。

  2. NIO 相关类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写。
    在这里插入图片描述

  3. NIO 有三大核心部分:  Channel(通道) Buffer(缓冲区) ,Selector(选择器)

  4. NIO 是 面向缓冲区 ,或者面向 块 编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络

  5. Java NIO 的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果
    目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可
    以继续做其他的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。

  6. 通俗理解:NIO 是可以做到用一个线程来处理多个操作的。假设有 10000 个请求过来,根据实际情况,可以分配50 或者 100 个线程来处理。不像之前的阻塞 IO 那样,非得分配 10000 个。

  7. HTTP2.0 使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比 HTTP1.1 大了好几个数量级。

  8. 案例说明 NIO 的 Buffer

import java.nio.IntBuffer;
import java.util.Random;

public class NioBasic {
    public static void main(String[] args) {
        //指定buffer长度
        IntBuffer buffer = IntBuffer.allocate(5);
        Random random1 = new Random();
        //加入随机数
        for (int i = 0; i < buffer.capacity(); i++) {
            buffer.put(random1.nextInt(1000));
        }
        //将buffer转换一下,读写切换
        buffer.flip();
        //读取
        while (buffer.hasRemaining()) {
            System.out.println(buffer.get());

        }
    }
}

1.2 NIO 和 BIO 的比较
  1. BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 I/O 的效率比流 I/O 高很多。
  2. BIO 是阻塞的,NIO 则是非阻塞的。
  3. BIO基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道 。
1.3 NIO 三大核心原理示意图

一张图描述 NIO 的 Selector 、 Channel 和 Buffer 的关系
在这里插入图片描述

  1. 每个channel 都会对应一个Buffer。
  2. Selector 对应一个线程, 一个线程对应多个channel(连接)。
  3. 该图反应了有三个channel 注册到 该selector
  4. 程序切换到哪个channel 是有事件决定的, Event 就是一个重要的概念
  5. Selector 会根据不同的事件,在各个通道上切换
  6. Buffer 就是一个内存块 , 底层是有一个数组
  7. 数据的读取写入是通过Buffer, 这个和BIO , BIO 中要么是输入流,或者是输出流, 不能双向,但是NIO的Buffer 是可以读也可以写, 需要 flip 方法切换
  8. channel 是双向的, 可以返回底层操作系统的情况, 比如Linux , 底层的操作系统通道就是双向的.
1.4 缓冲区(Buffer)
1.4.1基本介绍

缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer。如下图:、
在这里插入图片描述

1.4.2 Buffer 类及其子类
  1. 在 NIO 中,Buffer 是一个顶层父类,它是一个抽象类, 类的层级关系图:
    在这里插入图片描述
    常用Buffer子类一览
  • ByteBuffer,存储字节数据到缓冲区
  • ShortBuffer,存储字符串数据到缓冲区
  • CharBuffer,存储字符数据到缓冲区
  • IntBuffer,存储整数数据到缓冲区
  • LongBuffer,存储长整型数据到缓冲区
  • DoubleBuffer,存储小数到缓冲区
  • FloatBuffer,存储小数到缓冲区
  1. Buffer 类定义了所有的缓冲区都具有的四个属性来提供关于其所包含的数据元素的信息:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    3) Buffer类相关方法一览
public abstract class Buffer {
    //JDK1.4时,引入的api
    public final int capacity( )//返回此缓冲区的容量
    public final int position( )//返回此缓冲区的位置
    public final Buffer position (int newPositio)//设置此缓冲区的位置
    public final int limit( )//返回此缓冲区的限制
    public final Buffer limit (int newLimit)//设置此缓冲区的限制
    public final Buffer mark( )//在此缓冲区的位置设置标记
    public final Buffer reset( )//将此缓冲区的位置重置为以前标记的位置
    public final Buffer clear( )//清除此缓冲区, 即将各个标记恢复到初始状态,但是数据并没有真正擦除, 后面操作会覆盖
    public final Buffer flip( )//反转此缓冲区
    public final Buffer rewind( )//重绕此缓冲区
    public final int remaining( )//返回当前位置与限制之间的元素数
    public final boolean hasRemaining( )//告知在当前位置和限制之间是否有元素
    public abstract boolean isReadOnly( );//告知此缓冲区是否为只读缓冲区
 
    //JDK1.6时引入的api
    public abstract boolean hasArray();//告知此缓冲区是否具有可访问的底层实现数组
    public abstract Object array();//返回此缓冲区的底层实现数组
    public abstract int arrayOffset();//返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量
    public abstract boolean isDirect();//告知此缓冲区是否为直接缓冲区
}
  1. ByteBuffer
    从前面可以看出对于 Java 中的基本数据类型(boolean除外),都有一个 Buffer 类型与之相对应,最常用的自然是ByteBuffer 类(二进制数据),该类的主要方法如下:

在这里插入图片描述

public abstract class ByteBuffer {
    //缓冲区创建相关api
    public static ByteBuffer allocateDirect(int capacity)//创建直接缓冲区
    public static ByteBuffer allocate(int capacity)//设置缓冲区的初始容量
    public static ByteBuffer wrap(byte[] array)//把一个数组放到缓冲区中使用
    //构造初始化位置offset和上界length的缓冲区
    public static ByteBuffer wrap(byte[] array,int offset, int length)
     //缓存区存取相关API
    public abstract byte get( );//从当前位置position上get,get之后,position会自动+1
    public abstract byte get (int index);//从绝对位置get
    public abstract ByteBuffer put (byte b);//从当前位置上添加,put之后,position会自动+1
    public abstract ByteBuffer put (int index, byte b);//从绝对位置上put
 }

1.5 通道(Channel)
1.5.1 基本介绍
  1. NIO 的通道类似于流,但有些区别如下:
  • 通道可以同时进行读写,而流只能读或者只能写
  • 通道可以实现异步读写数据
  • 通道可以从缓冲读数据,也可以写数据到缓冲:
    在这里插入图片描述
  1. BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel)是双向的,可以读操作,也可以写操作。
  2. Channel在NIO中是一个接口 public interface Channel extends Closeable{}
  3. 常用的 Channel 类有:FileChannel、DatagramChannel、ServerSocketChannel 和 SocketChannel。【ServerSocketChanne 类似 ServerSocket , SocketChannel 类似 Socket】。
  4. FileChannel 用于文件的数据读写,DatagramChannel 用于 UDP 的数据读写,ServerSocketChannel 和 SocketChannel 用于 TCP 的数据读写。
    在这里插入图片描述
1.5.2 FileChannel 类

FileChannel主要用来对本地文件进行 IO 操作,常见的方法有

  • public int read(ByteBuffer dst) ,从通道读取数据并放到缓冲区中
  • public int write(ByteBuffer src) ,把缓冲区的数据写到通道中
  • public long transferFrom(ReadableByteChannel src, long position, long count),从目标通道中复制数据到当前通道
  • public long transferTo(long position, long count, WritableByteChannel target),把数据从当前通道复制给目标通道
1.5.3 应用实例 1-本地文件写数据
  1. 使用前面学习后的ByteBuffer(缓冲) 和 FileChannel(通道), 将 “hello,world” 写入到file01.txt 中
  2. 文件不存在就创建

代码演示:


import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOFileChannel01 {
    public static void main(String[] args) throws Exception {
        String str = "hello,world";
        //创建一个输出流
        FileOutputStream fileOutputStream = new FileOutputStream("d:\\file01.txt");
        //通过 fileOutputStream 获取对应的FileChannel
        //fileChannel真实的类型是 FileChannelImpl
       FileChannel fileChannel = fileOutputStream.getChannel();
       //创建一个缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //将str放入到byteBuffer
        byteBuffer.put(str.getBytes());
        //对butebuffer 进行flip
        byteBuffer.flip();
        //将byteBuffer数据写入到fileChannel
        fileChannel.write(byteBuffer);
        //关闭流
        fileOutputStream.close();
    }
}

1.5.4 应用实例 2-本地文件读数据

使用前面学习后的 ByteBuffer(缓冲) 和 FileChannel(通道), 将 file01.txt 中的数据读入到程序,并显示在控制台屏幕。
假定文件已经存在

代码演示:

import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOFileChannel02 {
    public static void main(String[] args) throws Exception {
        //创建文件的输入流
        File file = new File("d:\\file01.txt");
        FileInputStream fileInputStream = new FileInputStream(file);
        //通过FileInputStream获取对应的FileChannel 实际类型为FileChannelImpl
        FileChannel fileChannel = fileInputStream.getChannel();
        //创建缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate((int)file.length());
        //将通道的数据读入到Buffer中
        fileChannel.read(byteBuffer);
        //将byteBuffer的字节数据转成String
        System.out.println(new java.lang.String(byteBuffer.array()));

    }
}

1.5.5 应用实例 3-使用一个 Buffer 完成文件读取、写入

实例要求:
使用 FileChannel(通道) 和 方法 read , write,完成文件的拷贝。
拷贝一个文本文件 1.txt , 放在项目下即可。

在这里插入图片描述

代码演示:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NIOFileChannel03 {
    public static void main(String[] args) throws Exception {
        FileInputStream fileInputStream = new FileInputStream("1.txt");
        FileChannel fileChannel01 = fileInputStream.getChannel();

        FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
        FileChannel fileChannel02 = fileOutputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(512);

        while(true){
            byteBuffer.clear();
            int read = fileChannel01.read(byteBuffer);
            if(read == -1 ){
                break;
            }
            byteBuffer.flip();
            fileChannel02.write(byteBuffer);
        }
    }
}

1.5.6 应用实例 4-拷贝文件 transferFrom 方法

实例要求:

  1. 使用 FileChannel(通道) 和 方法 transferFrom ,完成文件的拷贝
  2. 拷贝一张图片

代码演示:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;

public class NIOFileChannel04 {
    public static void main(String[] args) throws Exception {
        FileInputStream fileInputStream = new FileInputStream("d:\\a.jpg");
        FileOutputStream fileOutputStream = new FileOutputStream("d:\\a2.jpg");

        FileChannel sourceCh = fileInputStream.getChannel();
        FileChannel destCh = fileOutputStream.getChannel();

        destCh.transferFrom(sourceCh,0,sourceCh.size());

        sourceCh.close();
        destCh.close();
        fileInputStream.close();
        fileOutputStream.close();
    }
}
1.5.7 关于 Buffer 和 Channel 的注意事项和细节
  1. ByteBuffer 支持类型化的 put 和 get, put 放入的是什么数据类型,get 就应该使用相应的数据类型来取出,否则可能有 BufferUnderflowException 异常。例如:以下代码中存入short类型,取出int类型,将报错:java.nio.BufferUnderflowException

import java.nio.ByteBuffer;

/**
 * @program: NettyPro
 * @description: Demo:put、get
 * @author: potterZha
 * @create: 2020-09-09 19:18
 **/
public class NIOByteBufferPutGet {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(64);
        //类型化方式放入数据
        buffer.putInt(100);
        buffer.putLong(9);
        buffer.putChar('中');
        buffer.putShort((short) 4);
        //取出
        buffer.flip();
        System.out.println();
        System.out.println(buffer.getInt());
        System.out.println(buffer.getLong());
        System.out.println(buffer.getChar());
        System.out.println(buffer.getInt());
    }
}

  1. 可以将一个普通 Buffer 转成只读 Buffer [举例说明]
import java.nio.ByteBuffer;

/**
 * @program: NettyPro
 * @description: 只读Buffer
 * @author: potterZha
 * @create: 2020-09-09 19:42
 **/
public class ReadOnlyBuffer {
    public static void main(String[] args) {
        //创建一个buffer
        ByteBuffer buffer = ByteBuffer.allocate(64);
        for(int i = 0 ; i < 64; i++){
            buffer.put((byte)(i));
        }
        buffer.flip();
        //得到一个只读的buffer
        ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
        System.out.println(readOnlyBuffer.clear());

        //读取
        while(readOnlyBuffer.hasRemaining()){
            System.out.println(readOnlyBuffer.get());
        }
        //如果想readOnlyBuffer中放入数据将抛出异常 java.nio.ReadOnlyBufferException
        //readOnlyBuffer.put((byte)4);
    }
}

  1. NIO 还提供了 MappedByteBuffer, 可以让文件直接在内存(堆外的内存)中进行修改, 而如何同步到文件,由 NIO 来完成. [举例说明]
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

/**
 * @program: NettyPro
 * @description: 可以让文件直接在堆外内存中修改操作系统需要拷贝一份
 * @author: potterZha
 * @create: 2020-09-09 19:50
 **/
public class MapperByteBufferTest {
    public static void main(String[] args) throws Exception {
        RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw");
        //获取对应的通道
        FileChannel channel = randomAccessFile.getChannel();
        /**
         * @param1 : FileChannel.MapMode.READ_WRITE 使用读写模式
         * @param2: 0: 可以直接修改的起始位置
         * @param3: 5: 是映射到内存的大小,即将1.txt 的多少个字节映射到内存 不包含 index 为5的位置,否则会报越界
         * @description : 可以直接修改的范围就是0~5 此处的 MappedByteBuffer 是 MappedByteBuffer抽象类 实际类型为 DirectByteBuffer
         */
        MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
        mappedByteBuffer.put(0,(byte)'H');
        mappedByteBuffer.put(3,(byte)'9');
        randomAccessFile.close();
    }
}


  1. 前面我们讲的读写操作,都是通过一个 Buffer 完成的,NIO 还支持 通过多个 Buffer (即 Buffer 数组) 完成读写操作,即 Scattering 和 Gathering 【举例说明】
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
public class ScatteringAndGathringTest {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
        //绑定端口到socket,并启动
        serverSocketChannel.socket().bind(inetSocketAddress);
        //创建数组
        ByteBuffer[] byteBuffers = new ByteBuffer[2];
        byteBuffers[0] = ByteBuffer.allocate(5);
        byteBuffers[1] = ByteBuffer.allocate(3);

        //等待客户端连接
        SocketChannel socketChannel = serverSocketChannel.accept();
        int messageLength = 8 ;
        while(true){
            int byteRead = 0 ;
            while(byteRead < messageLength ){
                long l = socketChannel.read(byteBuffers);
                byteRead += l;
                System.out.println("byteRead="+byteRead);
                Arrays.asList(byteBuffers).stream().map(buffer -> "position="
                +buffer.position() + ",limit=" + buffer.limit()).forEach(System.out::println);
            }
            //将所有的buffer进行flip
            Arrays.asList(byteBuffers).forEach(buffer -> buffer.flip());
            //将数据独处显示到客户端
            long byteWrite = 0 ;
            while(byteWrite < messageLength )
            {
                long l = socketChannel.write(byteBuffers);
                byteWrite += l;
            }
            //将所有的buffer进行clear
            Arrays.asList(byteBuffers).forEach(byteBuffer -> {
                byteBuffer.clear();
            });
            System.out.println("byteRead:=" + byteRead + " byteWrite" + byteWrite + ",messageLength = "+messageLength);

        }

    }
}

运行代码,同时本地打开cmd窗口,输入 telnet 127.0.0.1 700 后 ,接着输入 ‘CTRL+]’ 键,然后在窗口中输入 send hello1:

在这里插入图片描述
可以在控制台窗口看见:代码从两个buffer中完成读写操作

1.6 Selector(选择器)

1.6.1 基本介绍
  1. Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到Selector(选择器)。
  2. Selector 能够检测多个注册的通道上是否有事件发生(注意:多个Channel以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。
  3. 只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程
    避免了多线程之间的上下文切换导致的开销。
1.6.2 Selector 示意图和特点说明

在这里插入图片描述
特点再次说明:

  1. Netty 的 IO 线程 NioEventLoop 聚合了 Selector(选择器,也叫多路复用器),可以同时并发处理成百上千个客
    户端连接。

  2. 当线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。

  3. 线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。

  4. 由于读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 I/O 阻塞导致的线程挂起。

  5. 一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。

1.6.3 Selector 类相关方法

Selector 类是一个抽象类, 常用方法和说明如下

public abstract class Selector implements Closeable { 
	public static Selector open();//得到一个选择器对象
	public int select(long timeout);//监控所有注册的通道,当其中有 IO 操作可以进行时,将
	对应的 SelectionKey 加入到内部集合中并返回,参数用来设置超时时间
	public Set<SelectionKey> selectedKeys();//从内部集合中得到所有的 SelectionKey	
}

在这里插入图片描述

1.6.4 注意事项
  1. NIO 中的 ServerSocketChannel 功能类似 ServerSocket,SocketChannel 功能类似 Socket。
  2. selector 相关方法说明:
  • selector.select()//阻塞
  • selector.select(1000);//阻塞1000毫秒,在1000毫秒后返回
  • selector.wakeup();//唤醒selector
  • selector.selectNow();//不阻塞,立马返还

1.7 NIO 非阻塞 网络编程原理分析图

NIO 非阻塞 网络编程相关的(Selector、SelectionKey、ServerScoketChannel 和 SocketChannel) 关系梳理图

在这里插入图片描述
对上图的说明:

  • 当客户端连接时,会通过ServerSocketChannel 得到 SocketChannel
  • Selector 进行监听 select 方法, 返回有事件发生的通道的个数.
  • 将socketChannel注册到Selector上, register(Selector sel, int ops), 一个selector上可以注册多个SocketChannel
  • 注册后返回一个 SelectionKey, 会和该Selector 关联(集合)
  • 进一步得到各个 SelectionKey (有事件发生)
  • 在通过 SelectionKey 反向获取 SocketChannel , 方法 channel()可以通过
  • 得到的 channel , 完成业务处理

1.8 NIO 非阻塞 网络编程快速入门

要求:

  1. 编写一个 NIO 入门案例,实现服务器端和客户端之间的数据简单通讯(非阻塞)

NIOServer:

package com.mylove.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {
    public static void main(String[] args) throws IOException {
        //创建ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //得到一个Selector对象
        Selector selector = Selector.open();
        //绑定6666端口,在服务端监听
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        //设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        //把serverSocketChannel注册到 selector上,关心的事件为 OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("注册后的selectionKey 数量:" + selector.keys().size());
        //循环等待客户端的连接
        while(true){
            if(selector.select(5000) == 0 ){
                System.out.println("服务器等待了5秒,无连接");
                continue;
            }
            //如果返回的值 >0 就获取到了selecttionKey集合
            //1.如果返回结果>0 ,表示已经获取到关注的事件
            //2.selector.selectedKeys() 返回关注事件的集合
            //通过selectionKeys 反向获取通道
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            System.out.println("selectedKeys的数量是:" + selectionKeys.size());
            //遍历Set<SelectionKey>
            Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
            while (keyIterator.hasNext()){
                SelectionKey key = keyIterator.next();
                //根据key对应的通道发生的事件左相应的处理
                if(key.isAcceptable()){ // 如果是OP_ACCEPT 有新的客户端连接
                    //为该客户端生成一个SocketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();

                    System.out.println("客户端连接成功,生成了一个 socketchannel : " + socketChannel.hashCode());
                    socketChannel.configureBlocking(false);
                    //将socketChannel注册到selector 关注的事件为OP_READ 同事给socket关联一个Buffer
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                    System.out.println("客户端连接后注册的selectionKey 数量:" + selector.keys().size());

                }
                if(key.isReadable()){ //发生 OP_READ
                    SocketChannel channel = (SocketChannel)key.channel();
                    //获取到该channel关联的buffer
                    ByteBuffer buffer = (ByteBuffer)key.attachment();
                    channel.read(buffer);
                    System.out.println("from 客户端 " + new String(buffer.array()));
                }
                //手动从集合中移除当前的selectionKey,防止重复操作
                keyIterator.remove();
            }
        }
    }
}

NIOClient:

package com.mylove.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class NIOClient {
    public static void main(String[] args) throws IOException {
        //得到一个网络通道
        SocketChannel socketChannel = SocketChannel.open();
        //设置非阻塞
        socketChannel.configureBlocking(false);
        //提供服务器的ip和端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
        //连接服务器
        if(!socketChannel.connect(inetSocketAddress)){
            while (!socketChannel.finishConnect()){
                System.out.println("因为连接需要时间,客户端不会阻塞,可以做其他工作。。。。");
            }
            String str = "hello mylove";
            ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
            //发送数据将buffer数据写入到 channel
            socketChannel.write(buffer);
            System.in.read();
        }
    }
}

1.9 SelectionKey

  1. SelectionKey,表示 Selector 和网络通道的注册关系, 共四种:
  • int OP_ACCEPT:有新的网络连接可以 accept,值为 16
  • int OP_CONNECT:代表连接已经建立,值为 8
  • int OP_READ:代表读操作,值为 1
  • int OP_WRITE:代表写操作,值为 4

源码中:

public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
  1. SelectionKey 相关方法:

在这里插入图片描述

public abstract class SelectionKey {
	public abstract Selector selector();//得到与之关联的 Selector 对象
	public abstract SelectableChannel channel();//得到与之关联的通道
	public final Object attachment();//得到与之关联的共享数据
	public abstract SelectionKey interestOps(int ops);//设置或改变监听事件
	public final boolean isAcceptable();//是否可以 accept
	public final boolean isReadable();//是否可以读
	public final boolean isWritable();//是否可以写
}

1.10 ServerSocketChannel

  1. ServerSocketChannel 在服务器端监听新的客户端 Socket 连接
  2. 相关方法如下
    在这里插入图片描述
public abstract class ServerSocketChannel extends AbstractSelectableChannel  implements NetworkChannel{
	public static ServerSocketChannel open(),得到一个 ServerSocketChannel 通道
	public final ServerSocketChannel bind(SocketAddress local),设置服务器端端口号
	public final SelectableChannel configureBlocking(boolean block),设置阻塞或非阻塞模式,取值 false 表示采用非阻塞模式
	public SocketChannel accept(),接受一个连接,返回代表这个连接的通道对象
	public final SelectionKey register(Selector sel, int ops),注册一个选择器并设置监听事件
}

1.11 SocketChannel

SocketChannel,网络 IO 通道,具体负责进行读写操作。NIO 把缓冲区的数据写入通道,或者把通道里的数据读到缓冲区。
相关方法如下:

在这里插入图片描述

public abstract class SocketChannel   extends AbstractSelectableChannel  implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel{
	public static SocketChannel open();//得到一个 SocketChannel 通道
	public final SelectableChannel configureBlocking(boolean block);//设置阻塞或非阻塞模式,取值 false 表示采用非阻塞模式
	public boolean connect(SocketAddress remote);//连接服务器
	public boolean finishConnect();//如果上面的方法连接失败,接下来就要通过该方法完成连接操作
	public int write(ByteBuffer src);//往通道里写数据
	public int read(ByteBuffer dst);//从通道里读数据
	public final SelectionKey register(Selector sel, int ops, Object att);//注册一个选择器并设置监听事件,最后一个参数可以设置共享数据
	public final void close();//关闭通道
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值