Netty-NIO:核心组件(Buffer,Channel,Selector)详解

Nio最核心的组成部分就是Selector与Channe,还有Buffer,一个选择器中可以有多个通道和缓冲。

Buffer

Buffer 是一个顶级抽象类,所在java.nio.Buffer 中,里面提供了一些属性和方法来提供缓冲的功能操作。

    private int mark = -1; //标记
    private int position = 0;//位置
    private int limit;//最大的长度
    private int capacity;//容量

buffer 通过以上变量来实现 读写的转换,后面我们在代码中调试会看出来。

  //获取当前Buffer的容量
   public final int capacity() {
        return capacity;
    }
  //获取当前Buffer数组的位置
  public final int position() {
        return position;
    }
    
	获取当前Buffer数组最大长度
    public final int limit() {
        return limit;
    }
    //将缓冲区的位置设置到标记变量中
    public final Buffer mark() {
        mark = position;
        return this;
    }
    
    //读写转换,把当前位置设置到最大数量变量中,然后位置设置为0,在接下的读或写操作的时候,
    //再次进行位置的计数
     public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }
	//读取buffer中的数据
  public abstract Object array();
  
  //清空此缓冲区。位置设置为零,标记被丢弃
     public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }

以上大概就是这buffer中的部分抽象类方法,接下来我们来看看下面的子类:

IntBuffer,FloatBuffer,CharBuffer,DoubleBuffer,ShortBuffer,LongBuffer,ByteBuffer

代码实例:

 				 /**
         * 创建一个buff 大小为5
         */
        IntBuffer allocate = IntBuffer.allocate(5);
        //或者可以使用上面的字类去创建对应类型的buffer
        //FloatBuffer floatbuffer = FloatBuffer.allocate(5);
        //使用里面常用见的方法:
           for(int i=0;i

上面就是我们非常常见的buffer 使用了,put 一个数据进去,然后get 取出一个值,值得注意的就是一定要使用

flip() 去进行读写的转换,如果不加,那么是无法读取出数据。

调试:

以上面的代码为例子看看,buffer内部是这么去运用的呢。
Netty-NIO:核心组件(Buffer,Channel,Selector)详解_第1张图片

当我们执行创建buffer的时候,可以看到里面的属性 limit 最大数是5,说明后面添加的数据不能超过5个,最大的容量也是5
Netty-NIO:核心组件(Buffer,Channel,Selector)详解_第2张图片

当我们put一个值进去的时候,发现 position的值加了1个数,说明我们添加进去了一个值,可以理解这是数组的下标一样的东西。
Netty-NIO:核心组件(Buffer,Channel,Selector)详解_第3张图片

当我们全部添加完成的时候,可以看到position的值是5,与创建的容量一致的时候,这时候我们执行 flip 方法来读写转换的时候,会发生什么呢?
Netty-NIO:核心组件(Buffer,Channel,Selector)详解_第4张图片

可以看到,指针数被设置了为0,然后在读取数据的时候,他会再次作为读取的指针,一直读取到等limit等值的时候,就不会再次读取数据了,
Netty-NIO:核心组件(Buffer,Channel,Selector)详解_第5张图片

读取完毕。

注意点:

1.使用buffer的时候,存进去的数据类型和取出来的类型顺序必须保持一致,否则会抛出异常 BufferUnderflowException

代码实例:

  public static void main(String[] args) {
        ByteBuffer allocate = ByteBuffer.allocate(64);
        allocate.putInt(100);
        allocate.putLong(9);
        allocate.putChar('尚');
        allocate.putShort((short)4);
        allocate.flip();
        //这里按照上面的顺序get 出来是没有问题的,如果顺序不对,那么就会抛出异常 BufferUnderflowException
        System.out.println(allocate.getShort());
        System.out.println(allocate.getInt());
        System.out.println(allocate.getLong());
        System.out.println(allocate.getLong());
}

2.使用asReadOnlyBuffer() 可以把 buffer 转为只读的buffer,无法修改的buffer

ByteBuffer allocate = ByteBuffer.allocate(64);
        //buff 转为只读的
        ByteBuffer byteBuffer = allocate.asReadOnlyBuffer();
//        byteBuffer.putInt(1); 如果继续写 会抛出 ReadOnlyBufferException

Channel

通过最重要的字类是 NetworkChannel,WritableByteChannel。

channel 接口中只有两个方法,我们来看一下:

 	
 		//当前通道是否开启
    public boolean isOpen();
		
		//关闭当前通道
    public void close() throws IOException;

代码实例:

1.通过 FileChannel 通道,把一个内容写入到文件中:

/**
 * @Author qrn
 * @Date 2021/5/5 下午10:50
 * @Version 1.0
 * @blog https://blog.csdn.net/qq_41971087
 * FileChannel 写入数据到文件中,demo实例
 * 1.读取文件
 * 2.获取通道
 * 3.定义缓冲,把要写入文件的数据,放到缓冲中去
 * 4.把缓冲中到数据写入到通道中
 * 5.关闭流
 *
 */
public class NioFileChannel {
    public static void main(String[] args) throws  Exception{
        String hell= "hello,你好netty";
        //获取文件
        FileOutputStream fileOutputStream = new FileOutputStream("/Users/qurenneng/hello.txt");
        //获取nio中到通道
        FileChannel channel = fileOutputStream.getChannel();
        //定义缓冲区,bytebuffrt
        ByteBuffer allocate = ByteBuffer.allocate(1024);
        //缓冲区中写入字节数据
        allocate.put(hell.getBytes());
        //读写转换
        allocate.flip();
        //写入到file 通道中去
        channel.write(allocate);
        //关闭流
        fileOutputStream.close();
    }
}

2.读取文件中的内容,打印在控制台:

/**
 * @Author qrn
 * @Date 2021/5/5 下午11:09
 * @Version 1.0
 * @blog https://blog.csdn.net/qq_41971087
 * 使用 nio fiechannel 读取文件数据打印出来:
 */
public class NioFileChannelRean {
    public static void main(String[] args) throws  Exception{
        File file = new File("/Users/qurenneng/hello.txt");
        //读取到文件
        FileInputStream fileInputStream = new FileInputStream(file);

        FileChannel channel = fileInputStream.getChannel();

        ByteBuffer allocate = ByteBuffer.allocate((int) file.length());

        channel.read(allocate);
        System.out.println(new String(allocate.array()));
    }
}

3.文件拷贝,从一个文件读取内容后,写入到另外一个文件当中,使用同一个buffer,读写转换。

/**
 * @Author qrn
 * @Date 2021/5/7 下午10:46
 * @Version 1.0
 * @blog https://blog.csdn.net/qq_41971087
 * nio 实例 文件拷贝:
 */
public class NioFileChannelCobe {

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

        FileOutputStream fileOutputStream = new FileOutputStream("2");
        FileChannel channel = fileOutputStream.getChannel();

        ByteBuffer allocate = ByteBuffer.allocate(512);
        while (true){
            allocate.clear();//清空缓存
            int read = fileChannel.read(allocate);
            if(read == -1)break;
            allocate.flip(); //反转

            channel.write(allocate);
        }
        fileInputStream.close();
    }
}

4.使用transferFrom 完成图片的复制:

public class NioFileChannelimage {

    public static void main(String[] args) throws Exception{
        FileInputStream fileInputStream = new FileInputStream("a.png");
        FileChannel fileChannel = fileInputStream.getChannel();

        FileOutputStream fileOutputStream = new FileOutputStream("b.png");
        FileChannel channel = fileOutputStream.getChannel();

        //文件拷贝:拷贝那个通道 从什么位置开始,文件有多大
        channel.transferFrom(fileChannel,0,fileChannel.size());

        fileChannel.close();
        channel.close();
        fileInputStream.close();
        fileOutputStream.close();
    }
}

Selector

选择器,是一个顶级的抽象类,里面最重要的就是 SelectionKey,大致可以理解为,没个通道注册进去选择器的时候,都是会得到一个

SelectionKey在选择器当中,这样我们就可以根据SelectionKey 来获取channel,来看下SelectionKey中的方法:

   //返回创建了当前SelectionKey 的通道
   public abstract SelectableChannel channel();
   
   //读的
    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;

	//通过这写常量的定义,就能知道,当前通道是读,还是写,还在连接中,等等,这样我们就可以在通信中,获取到连接用户的状态。
   

于此在Selector中,会有一个set 去存储 SelectionKey,去重。

    public abstract Set keys();

代码实例:

/**
 * @Author qrn
 * @Date 2021/5/9 下午3:29
 * @Version 1.0
 * @blog https://blog.csdn.net/qq_41971087
 *
 * nio 服务端:
 */
public class NioService {

    public static void main(String[] args) throws  Exception{
        ServerSocketChannel open = ServerSocketChannel.open();

        Selector selector = Selector.open();

        open.socket().bind(new InetSocketAddress(6666));
       //设置为非阻塞
        open.configureBlocking(false);

        open.register(selector, SelectionKey.OP_ACCEPT);

        while (true){

            if(selector.select(1000) == 0){
                System.out.println("服务器等待了1秒,无连接");
                continue;
            }
            Set selectionKeys = selector.selectedKeys();
            Iterator iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey next = iterator.next();
                if(next.isAcceptable()){
                    SocketChannel accept = open.accept();
                    accept.configureBlocking(false);//非阻塞的
                    accept.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }

                if(next.isReadable()){
                    SocketChannel channel = (SocketChannel)next.channel();

                    ByteBuffer attachment = (ByteBuffer)next.attachment();
                    channel.read(attachment);
                    System.out.println(new String(attachment.array()));
                }
                //删除key
                iterator.remove();
            }

        }
    }
}

/**
 * @Author qrn
 * @Date 2021/5/9 下午4:02
 * @Version 1.0
 * @blog https://blog.csdn.net/qq_41971087
 * nio 客户端
 */
public class NioClient {

    public static void main(String[] args) throws  Exception{

        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);

        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1",6666);

        //连接服务器
        if(!socketChannel.connect(inetSocketAddress)){

            while (!socketChannel.finishConnect()){
                System.out.println("因为连接需要时间");
            }

        }

        String string = "小明";
        ByteBuffer wrap = ByteBuffer.wrap(string.getBytes());
        socketChannel.write(wrap);


    }
}

如果这篇文章,有帮助到大家的,请给作者一个一键三连,谢谢。

你可能感兴趣的:(Netty,java,netty,nio,redis)